# Excessive Entities How to keep the `entities` layer clean and avoid over-extracting business logic into entities. Excessive entities cause ambiguity (what code belongs where), coupling, and constant import dilemmas as code scatters across sibling entities. ## Why this matters The `entities` layer is one of the lower layers and is widely accessible. Every layer except `shared` can import from it. That global nature means changes to `entities` propagate widely, requiring careful design to avoid costly refactors. Adding an entity is cheap; removing one after many consumers depend on it is expensive. ## How to keep entities clean ### 0. Consider having no entities layer An FSD application without an `entities` layer is still FSD. Skipping the layer simplifies the architecture and keeps it available for future scaling. **Thin clients** (where the backend handles most data processing and the client mostly exchanges data) usually do not need an entities layer. **Thick clients** (significant client-side business logic) are better candidates for entities. The classification is not strictly binary. Different parts of the same application may behave as thick or thin clients. ```text // Thin client without entities layer (still valid FSD) src/ app/ pages/ dashboard/ profile/ shared/ api/ ui/ ``` ### 1. Avoid preemptive slicing FSD v2.1 encourages **deferred decomposition** of slices. Place code in the `model` segment of the consuming page, widget, or feature first. Move it to `entities` later, when business requirements stabilize and reuse is confirmed across multiple consumers. The later code moves to `entities`, the less dangerous the refactor. Code in `entities` can affect every higher-layer slice that imports it. ```text // Iteration 1: code lives where it is used pages/profile/ model/ profile-validation.ts ← page-specific for now // Iteration 2 (after the same logic is needed in 2+ places): entities/profile/ model/ profile-validation.ts ← extracted only after reuse is real ``` ### 2. Avoid unnecessary entities Do not create an entity for every piece of business logic. Use types from `shared/api` and place logic in the `model` segment of the current slice. For genuinely reusable business logic, use the `model` segment within an entity slice while keeping data definitions in `shared/api`. ```text shared/ api/ endpoints/ order.ts ← OrderDto type and request functions entities/ order/ model/ apply-discount.ts ← Business logic that uses OrderDto index.ts ``` The DTO lives in `shared/api/endpoints/order.ts`. Business logic that operates on it (calculating discounts, applying promotions) lives in `entities/order/model/`. Do not mirror every API endpoint with a corresponding entity. ### 3. Exclude CRUD operations from entities CRUD operations involve boilerplate code without significant business logic. Putting them in `entities` clutters the layer and obscures the code that genuinely matters. Place CRUD in `shared/api`: ```text shared/ api/ client.ts endpoints/ order.ts ← getOrder, createOrder, updateOrder, deleteOrder products.ts ← Standard CRUD for products cart.ts ← Standard CRUD for cart index.ts ``` For complex CRUD with atomic updates, rollbacks, or transactions, evaluate whether the operation is genuinely business logic. If so, the `entities` layer may be appropriate. If not, keep it in `shared/api`. ### 4. Store authentication data in shared Prefer `shared` over creating a `user` entity for auth tokens and session DTOs. These are context-specific to authentication and unlikely to be reused outside that scope. Wrapping a login response in a `user` entity also tends to drag entities into cross-layer imports or `@x` chains, complicating the architecture. The Auth guide also documents **In Entities** (a `user` entity) as a valid placement when the project already has an entities layer and the data is genuinely reused. **In Pages/Widgets** is discouraged for both guides. **`shared/auth` (or `shared/api`) is the recommended default.** Choose it when: - The project has no entities layer yet - Auth state is just a token plus minimal user info (id, email, role) - Token management logic (refresh, expiration) is the main concern, not user profile data ```text shared/ auth/ use-auth.ts ← Token + minimal user info index.ts api/ client.ts ← API client reads token from shared/auth endpoints/ order.ts index.ts ``` This approach pairs naturally with an API client middleware that injects the token into authenticated requests. **A `user` entity is the right call when:** - The project already has an entities layer - Auth and profile data are tightly coupled (current user info is reused across pages for non-auth purposes like comments, posts, mentions) - Token management has complex business logic (invalidation policies, multi-device session tracking) that benefits from co-location with the user model ```text entities/ user/ model/ current-user.ts ← Token + full user model + business logic user.ts ← Generic user type, used for other users too api/ get-current-user.ts index.ts ``` When using the entity approach, the API client (in `shared/api`) needs access to the token without violating the import rule. The official Auth guide describes three solutions: pass the token manually on each request, expose it through a context or `localStorage` with the key kept in `shared/api`, or inject the token into the API client whenever the entity store updates. **Pages and widgets are discouraged.** Avoid placing the token store in a page's `model/` segment or in a widget. App-wide state belongs in Shared or Entities, not in route-bound or block-bound layers. ### Decision summary | Project state | Recommended location | | --- | --- | | No entities layer (yet), simple token + minimal user info | `shared/auth` | | Entities layer exists, auth and profile tightly coupled | `entities/user` | | Complex token logic, no profile reuse yet | `shared/auth` (split from `shared/api`) | | Token storage in a single page or widget | Avoid; promote to Shared or Entities | A `user` entity created **only** to wrap a login response is premature. Wait until profile data is consumed for non-auth purposes (avatars in comments, names in posts) before introducing the entity. ### 5. Minimize cross-imports FSD permits cross-imports between entities via `@x`, but they introduce technical issues including circular dependencies. Design entities within **isolated business contexts** so cross-imports become unnecessary. **Non-isolated context (avoid):** ```text entities/ order/ @x/ model/ order-item/ @x/ model/ order-customer-info/ @x/ model/ ``` Three sibling entities all referencing each other through `@x`. This is a sign that the boundaries are wrong. **Isolated context (preferred):** ```text entities/ order-info/ model/ order-info.ts ← order, items, and customer info together index.ts ``` One entity encapsulates the related logic. No `@x`, no cross-imports, no circular dependency risk. The general rule: when several entities have `@x` dependencies on each other, treat that as a signal to merge the boundaries, not as something to manage. ## Decision tree for AI agents ```text A new piece of business logic needs a home. │ ├─ Is the project a thin client? │ └─ YES → Skip entities. Place in shared/ + page model. │ ├─ Is the logic used in only one place right now? │ └─ YES → Keep in the consuming slice's model/. Defer extraction. │ ├─ Is it a CRUD operation without business meaning? │ └─ YES → shared/api/endpoints/.ts │ ├─ Is it auth data (tokens, session, login DTOs)? │ ├─ Project has no entities layer yet? │ │ └─ YES → shared/auth/ │ ├─ Auth and profile data tightly coupled, entities layer exists? │ │ └─ YES → entities/user/ │ └─ Otherwise → shared/auth/ (default). │ Avoid placing in a page or widget. │ ├─ Is it just a TypeScript type for an API response? │ └─ YES → shared/api/. No entity needed for types alone. │ └─ Is it reusable domain logic confirmed in 2+ consumers? └─ YES → Create entities//model/. Verify the boundary is isolated and does not require @x to communicate with sibling entities. ``` ## Anti-patterns - **Creating entities preemptively.** Wait for confirmed reuse in 2+ consumers, not anticipated reuse. - **Mirroring every API endpoint with an entity.** API endpoints belong in `shared/api`. Entities exist for business logic, not for paralleling the backend structure. - **Creating a `user` entity *only* to wrap a login response.** A `user` entity is justified when profile data is reused across non-auth flows (avatars in comments, names in posts) or when token logic is genuinely tied to user business logic. Until that reuse appears, `shared/auth` is simpler. Storing tokens in a page or widget is discouraged regardless of the project shape. - **Splitting one domain into many entities (`order`, `order-item`, `order-customer-info`).** This produces `@x` chains. Merge into a single isolated context (`order-info` or `order`). - **Putting CRUD wrappers in entities.** They clutter the layer. CRUD goes in `shared/api/endpoints/`. ## See also - `references/cross-import-patterns.md`: how to handle cross-imports when they appear, and why `@x` is a last resort. - `references/layer-structure.md`: layer responsibilities and the entities segment shape.