|
| 1 | +--- |
| 2 | +sidebar_position: 4 |
| 3 | +pagination_next: reference/layers |
| 4 | +--- |
| 5 | + |
| 6 | +# Excessive Entities |
| 7 | + |
| 8 | +Feature-Sliced Design에서 `entities` Layer는 하위 Layer에 속하며, 재사용 가능한 도메인(비지니스) 로직을 담는 곳입니다. |
| 9 | +이 Layer는 접근성이 높아서, `shared`를 제외한 거의 모든 Layer가 `entities`를 참조할 수 있어 접근 범위가 넓습니다. |
| 10 | + |
| 11 | +다만 접근성이 높은 만큼 주의할 점도 있습니다. |
| 12 | +`entities`에 코드가 추가/수정되거나 파일 경로가 바뀌면, 상위 Layer의 여러 Slice에서 그 변경을 함께 따라가야 할 수 있습니다. |
| 13 | +그래서 리팩토링 비용이 커지기 전에, `entities`는 특히 경계와 역할을 더 명확하게 정의하고 관리하는 편이 좋습니다. |
| 14 | + |
| 15 | +`entities`에 코드가 불필요하게 많이 쌓이면 보통 다음 문제가 같이 나타납니다. |
| 16 | + |
| 17 | +- **경계가 모호해집니다**: “이 로직을 `entities`에 두는 게 맞나?” 같은 판단이 계속 필요해집니다. |
| 18 | +- **결합도가 올라갑니다**: 여러 도메인이 서로 얽히면서 수정이 어려워집니다. |
| 19 | +- **Import 딜레마가 생깁니다**: 코드가 동일 Layer의 다른 entity Slice로 흩어지면서, Import가 복잡해지고 선택이 어려워집니다. |
| 20 | + |
| 21 | +## How to keep `entities` Layer clean |
| 22 | + |
| 23 | +### 0. `entities` Layer 없이 시작하는 것도 가능합니다 |
| 24 | + |
| 25 | +`entities` Layer를 만들지 않으면 FSD가 아니라고 생각하기 쉽지만, 그렇지 않습니다. |
| 26 | +애플리케이션에 `entities` Layer가 없어도 FSD 규칙이 깨지지 않습니다. |
| 27 | +오히려 구조가 단순해지고, 나중에 규모가 커졌을 때 `entities`를 도입할 수 있도록 확장성을 확보할 수 있습니다. |
| 28 | + |
| 29 | +예를 들어 애플리케이션이 **thin client**에 가깝다면, 대부분 `entities` Layer가 필요하지 않습니다. |
| 30 | + |
| 31 | +:::info[What are thick and thin clients?] |
| 32 | + |
| 33 | +thick client와 thin client의 구분은 “데이터 처리와 비즈니스 로직을 어디서 처리하느냐”를 기준으로 합니다. |
| 34 | + |
| 35 | +- **thin client**: 대부분의 처리를 백엔드에서 수행합니다. 클라이언트에서는 비즈니스 로직을 최소화하고, 주로 백엔드와 데이터를 주고받습니다. |
| 36 | +- **thick client**: 클라이언트에서 의미 있는 비즈니스 로직을 많이 처리합니다. 이런 경우 `entities` Layer를 두면 도메인 로직을 구조적으로 정리하기가 더 수월합니다. |
| 37 | + |
| 38 | +이 구분은 딱 둘 중 하나로만 나뉘지 않습니다. |
| 39 | +같은 애플리케이션이라도 일부는 thick client처럼, 다른 일부는 thin client처럼 동작할 수 있습니다. |
| 40 | + |
| 41 | +::: |
| 42 | + |
| 43 | +### 1. Slice를 처음부터 잘게 나누지 않습니다 |
| 44 | + |
| 45 | +FSD 2.1은 Slice를 미리 잘게 쪼개기보다, 필요해졌을 때 분리하는 접근을 권장합니다. |
| 46 | +이 원칙은 `entities` Layer에도 그대로 적용됩니다. |
| 47 | + |
| 48 | +처음에는 다음처럼 시작해도 됩니다. |
| 49 | + |
| 50 | +1. page 또는 widget/feature Slice의 `model` Segment에 로직을 둡니다. |
| 51 | +2. 요구사항이 어느 정도 안정되고, “이 로직은 여러 곳에서 재사용된다”가 분명해졌을 때 `entities`로 옮기는 리팩토링을 고려합니다. |
| 52 | + |
| 53 | +여기서 중요한 점은 "언제 옮기느냐"입니다. |
| 54 | +코드를 `entities`로 옮기는 시점이 늦을수록, 리팩토링 리스크가 줄어듭니다. |
| 55 | +`entities`의 코드는 `shared`를 제외한 모든 Layer에서 쓰일 수 있어서, 변경이 여러 곳의 동작에 영향을 줄 수 있기 때문입니다. |
| 56 | + |
| 57 | +### 2. 불필요한 Entities를 만들지 않습니다 |
| 58 | + |
| 59 | +비즈니스 로직이 있다고 해서 항상 entity를 만들어야 하는 것은 아닙니다. |
| 60 | +먼저 `shared/api`의 타입을 활용하고, 로직은 현재 Slice의 `model` Segment에 두는 방식을 우선 고려합니다. |
| 61 | + |
| 62 | +재사용 가능한 비즈니스 로직이 정말 필요하다면, 다음처럼 역할을 나누는 편이 좋습니다. |
| 63 | + |
| 64 | +- 데이터 정의(예: 백엔드 응답 타입)는 `shared/api`에 둡니다. |
| 65 | +- 재사용 로직은 entity Slice의 `model` Segment에 둡니다. |
| 66 | + |
| 67 | + |
| 68 | +```plaintext |
| 69 | +📂 entities |
| 70 | + 📂 order |
| 71 | + 📄 index.ts |
| 72 | + 📂 model |
| 73 | + 📄 apply-discount.ts // Business logic using OrderDto from shared/api |
| 74 | +📂 shared |
| 75 | + 📂 api |
| 76 | + 📄 index.ts |
| 77 | + 📂 endpoints |
| 78 | + 📄 order.ts |
| 79 | +``` |
| 80 | + |
| 81 | +### 3. CRUD는 `entities`에 두지 않는 편이 좋습니다 |
| 82 | + |
| 83 | +CRUD는 필수지만, 많은 경우 비즈니스 의미가 크지 않은 반복 코드가 됩니다. |
| 84 | +이런 코드가 `entities`에 쌓이면 Layer가 지저분해지고, 중요한 로직이 눈에 잘 띄지 않게 됩니다. |
| 85 | + |
| 86 | +```plaintext |
| 87 | +📂 shared |
| 88 | + 📂 api |
| 89 | + 📄 client.ts |
| 90 | + 📄 index.ts |
| 91 | + 📂 endpoints |
| 92 | + 📄 order.ts // Contains all order-related CRUD operations |
| 93 | + 📄 products.ts |
| 94 | + 📄 cart.ts |
| 95 | +``` |
| 96 | + |
| 97 | +대신 CRUD는 `shared/api`에 둡니다. |
| 98 | + |
| 99 | +CRUD가 단순 호출 수준을 넘어, 예를 들어 여러 요청을 묶어서 일관성을 보장해야 하거나, 실패 시 rollback, transaction 같은 처리가 필요한 경우에는 `entities`가 맞는지 다시 판단할 수 있지만, 신중하게 적용하는 편이 좋습니다. |
| 100 | + |
| 101 | +### 4. 인증 데이터는 `shared`에 둡니다 |
| 102 | + |
| 103 | +토큰이나 로그인 응답에 포함된 사용자 DTO처럼 인증 과정에서만 쓰이는 데이터는 `user` entity를 만들기보다 `shared`에 두는 편이 좋습니다. |
| 104 | +이 데이터는 인증 Context에 종속적이며, 인증 범위를 벗어나 재사용될 가능성이 낮습니다. |
| 105 | + |
| 106 | +- 로그인 응답은 상황에 따라 포함하는 정보가 달라질 수 있습니다(예: 공개/비공개 프로필). |
| 107 | +- 이런 데이터를 entity로 올려버리면, 다른 곳에서 재사용하려다가 `shared`와 `entities` 사이 의존 관계가 꼬이거나, |
| 108 | +cross-import를 표시하기 위한 `@x` 사용이 늘면서 구조가 더 복잡해질 수 있습니다. |
| 109 | + |
| 110 | +따라서 인증과 직접 관련된 데이터는 `shared/auth` 또는 `shared/api`에 두는 방식을 권장합니다. |
| 111 | + |
| 112 | + |
| 113 | +```plaintext |
| 114 | +📂 shared |
| 115 | + 📂 auth |
| 116 | + 📄 use-auth.ts // authenticated user info or token |
| 117 | + 📄 index.ts |
| 118 | + 📂 api |
| 119 | + 📄 client.ts |
| 120 | + 📄 index.ts |
| 121 | + 📂 endpoints |
| 122 | + 📄 order.ts |
| 123 | +``` |
| 124 | + |
| 125 | +인증 구현은 [Authentication 가이드](/docs/guides/examples/auth)를 참고하세요. |
| 126 | + |
| 127 | +### 5. Cross-import를 최소화합니다 |
| 128 | + |
| 129 | +FSD는 `@x` 표기를 통해 cross-import를 허용하지만, 이 방식은 기술적 문제(예: 순환 의존)를 만들 수 있습니다. |
| 130 | +이를 피하려면 entity를 서로 섞이지 않게 분리된 도메인 단위로 설계해 cross-import 자체가 필요 없도록 만드는 편이 좋습니다. |
| 131 | + |
| 132 | +예를 들어 주문 아이템, 고객 정보처럼 항상 함께 움직이는 로직이 있다면, 이를 여러 entity로 쪼개기보다 order-info 같은 하나의 entity slice(모듈) 안에 캡슐화하는 방식이 더 낫습니다. |
| 133 | + |
| 134 | + |
| 135 | +**Non-Isolated Business Context (Avoid):** |
| 136 | + |
| 137 | +```plaintext |
| 138 | +📂 entities |
| 139 | + 📂 order |
| 140 | + 📂 @x |
| 141 | + 📂 model |
| 142 | + 📂 order-item |
| 143 | + 📂 @x |
| 144 | + 📂 model |
| 145 | + 📂 order-customer-info |
| 146 | + 📂 @x |
| 147 | + 📂 model |
| 148 | +``` |
| 149 | + |
| 150 | +**Isolated Business Context (Preferred):** |
| 151 | + |
| 152 | +```plaintext |
| 153 | +📂 entities |
| 154 | + 📂 order-info |
| 155 | + 📄 index.ts |
| 156 | + 📂 model |
| 157 | + 📄 order-info.ts |
| 158 | +``` |
| 159 | + |
| 160 | +이렇게 하면 관련 코드가 한 곳에 모여 구조가 단순해지고, 강하게 결합된 로직이 여러 모듈로 흩어져 생기는 **변경 여파**도 줄일 수 있습니다. |
| 161 | +또한 강하게 결합된 로직을 외부에서 수정/변경해야 하는 상황을 줄일 수 있습니다. |
| 162 | + |
0 commit comments