콘텐츠로 이동

Excessive Entities

Feature-Sliced Design에서 entities Layer는 하위 Layer에 속하며, 재사용 가능한 도메인(비지니스) 로직을 담는 곳입니다.
이 Layer는 접근성이 높아서, shared를 제외한 거의 모든 Layer가 entities를 참조할 수 있어 접근 범위가 넓습니다.

다만 접근성이 높은 만큼 주의할 점도 있습니다.
entities에 코드가 추가/수정되거나 파일 경로가 바뀌면, 상위 Layer의 여러 Slice에서 그 변경을 함께 따라가야 할 수 있습니다.
그래서 리팩토링 비용이 커지기 전에, entities는 특히 경계와 역할을 더 명확하게 정의하고 관리하는 편이 좋습니다.

entities에 코드가 불필요하게 많이 쌓이면 보통 다음 문제가 같이 나타납니다.

  • 경계가 모호해집니다: “이 로직을 entities에 두는 게 맞나?” 같은 판단이 계속 필요해집니다.
  • 결합도가 올라갑니다: 여러 도메인이 서로 얽히면서 수정이 어려워집니다.
  • Import 딜레마가 생깁니다: 코드가 동일 Layer의 다른 entity Slice로 흩어지면서, Import가 복잡해지고 선택이 어려워집니다.

0. entities Layer 없이 시작하는 것도 가능합니다

섹션 제목: “0. entities Layer 없이 시작하는 것도 가능합니다”

entities Layer를 만들지 않으면 FSD가 아니라고 생각하기 쉽지만, 그렇지 않습니다.
애플리케이션에 entities Layer가 없어도 FSD 규칙이 깨지지 않습니다.
오히려 구조가 단순해지고, 나중에 규모가 커졌을 때 entities를 도입할 수 있도록 확장성을 확보할 수 있습니다.

예를 들어 애플리케이션이 thin client에 가깝다면, 대부분 entities Layer가 필요하지 않습니다.

1. Slice를 처음부터 잘게 나누지 않습니다

섹션 제목: “1. Slice를 처음부터 잘게 나누지 않습니다”

FSD 2.1은 Slice를 미리 잘게 쪼개기보다, 필요해졌을 때 분리하는 접근을 권장합니다.
이 원칙은 entities Layer에도 그대로 적용됩니다.

처음에는 다음처럼 시작해도 됩니다.

  1. page 또는 widget/feature Slice의 model Segment에 로직을 둡니다.
  2. 요구사항이 어느 정도 안정되고, “이 로직은 여러 곳에서 재사용된다”가 분명해졌을 때 entities로 옮기는 리팩토링을 고려합니다.

여기서 중요한 점은 “언제 옮기느냐”입니다.
코드를 entities로 옮기는 시점이 늦을수록, 리팩토링 리스크가 줄어듭니다. entities의 코드는 shared를 제외한 모든 Layer에서 쓰일 수 있어서, 변경이 여러 곳의 동작에 영향을 줄 수 있기 때문입니다.

2. 불필요한 Entities를 만들지 않습니다

섹션 제목: “2. 불필요한 Entities를 만들지 않습니다”

비즈니스 로직이 있다고 해서 항상 entity를 만들어야 하는 것은 아닙니다.
먼저 shared/api의 타입을 활용하고, 로직은 현재 Slice의 model Segment에 두는 방식을 우선 고려합니다.

재사용 가능한 비즈니스 로직이 정말 필요하다면, 다음처럼 역할을 나누는 편이 좋습니다.

  • 데이터 정의(예: 백엔드 응답 타입)는 shared/api에 둡니다.
  • 재사용 로직은 entity Slice의 model Segment에 둡니다.
  • 디렉터리entities/
    • 디렉터리order/
      • index.ts
      • 디렉터리model/
        • apply-discount.ts Business logic using OrderDto from shared/api
  • 디렉터리shared/
    • 디렉터리api/
      • index.ts
      • 디렉터리endpoints/
        • order.ts

3. CRUD는 entities에 두지 않는 편이 좋습니다

섹션 제목: “3. CRUD는 entities에 두지 않는 편이 좋습니다”

CRUD는 필수지만, 많은 경우 비즈니스 의미가 크지 않은 반복 코드가 됩니다.
이런 코드가 entities에 쌓이면 Layer가 지저분해지고, 중요한 로직이 눈에 잘 띄지 않게 됩니다.

  • 디렉터리shared/
    • 디렉터리api/
      • client.ts
      • index.ts
      • 디렉터리endpoints/
        • order.ts Contains all order-related CRUD operations
        • products.ts
        • cart.ts

대신 CRUD는 shared/api에 둡니다.

CRUD가 단순 호출 수준을 넘어, 예를 들어 여러 요청을 묶어서 일관성을 보장해야 하거나, 실패 시 rollback, transaction 같은 처리가 필요한 경우에는 entities가 맞는지 다시 판단할 수 있지만, 신중하게 적용하는 편이 좋습니다.

4. 인증 데이터는 shared에 둡니다

섹션 제목: “4. 인증 데이터는 shared에 둡니다”

토큰이나 로그인 응답에 포함된 사용자 DTO처럼 인증 과정에서만 쓰이는 데이터는 user entity를 만들기보다 shared에 두는 편이 좋습니다.
이 데이터는 인증 Context에 종속적이며, 인증 범위를 벗어나 재사용될 가능성이 낮습니다.

  • 로그인 응답은 상황에 따라 포함하는 정보가 달라질 수 있습니다(예: 공개/비공개 프로필).
  • 이런 데이터를 entity로 올려버리면, 다른 곳에서 재사용하려다가 sharedentities 사이 의존 관계가 꼬이거나,
    cross-import를 표시하기 위한 @x 사용이 늘면서 구조가 더 복잡해질 수 있습니다.

따라서 인증과 직접 관련된 데이터는 shared/auth 또는 shared/api에 두는 방식을 권장합니다.

  • 디렉터리shared/
    • 디렉터리auth/
      • use-auth.ts authenticated user info or token
      • index.ts
    • 디렉터리api/
      • client.ts
      • index.ts
      • 디렉터리endpoints/
        • order.ts

인증 구현은 Authentication 가이드를 참고하세요.

FSD는 @x 표기를 통해 cross-import를 허용하지만, 이 방식은 기술적 문제(예: 순환 의존)를 만들 수 있습니다.
이를 피하려면 entity를 서로 섞이지 않게 분리된 도메인 단위로 설계해 cross-import 자체가 필요 없도록 만드는 편이 좋습니다.

예를 들어 주문 아이템, 고객 정보처럼 항상 함께 움직이는 로직이 있다면, 이를 여러 entity로 쪼개기보다 order-info 같은 하나의 entity slice(모듈) 안에 캡슐화하는 방식이 더 낫습니다.

Non-Isolated Business Context (Avoid):

  • 디렉터리entities/
    • 디렉터리order/
      • 디렉터리@x/
      • 디렉터리model/
    • 디렉터리order-item/
      • 디렉터리@x/
      • 디렉터리model/
    • 디렉터리order-customer-info/
      • 디렉터리@x/
      • 디렉터리model/

Isolated Business Context (Preferred):

  • 디렉터리entities/
    • 디렉터리order-info/
      • index.ts
      • 디렉터리model/
        • order-info.ts

이렇게 하면 관련 코드가 한 곳에 모여 구조가 단순해지고, 강하게 결합된 로직이 여러 모듈로 흩어져 생기는 변경 여파도 줄일 수 있습니다.
또한 강하게 결합된 로직을 외부에서 수정/변경해야 하는 상황을 줄일 수 있습니다.