Skip to main content

Excessive Entities

The entities layer in Feature-Sliced Design is one of the lower layers that's primarily for business logic. That makes it widely accessible โ€” all layers except for shared can access it. However, its global nature means that changes to entities can have a widespread impact, requiring careful design to avoid costly refactors.

Excessive entities can lead to ambiguity (what code belongs to this layer), coupling, and constant import dilemmas (code scattered across sibling entities).

How to keep entities layer cleanโ€‹

0. Consider having no entities layerโ€‹

You might think that your application won't be Feature-Sliced if you don't include this layer, but it is completely fine for the application to have no entities layer. It doesn't break FSD in any way, on the contrary, it simplifies the architecture and keeps the entities layer available for future scaling. For example, if your application acts as a thin client, most likely it doesn't need entities layer.

What are thick and thin clients?

Thick vs. thin client distinction refers to how the application processes data:

  • Thin clients rely on the backend for most data processing. Client-side business logic is minimal and involves only exchanging data with the backend.
  • Thick clients handle significant client-side business logic, making them suitable candidates for the entities layer.

Keep in mind that this classification is not strictly binary, and different parts of the same application may act as a "thick" or a "thin" client.

1. Avoid preemptive slicingโ€‹

In contrast to previous versions, FSD 2.1 encourages deferred decomposition of slices instead of preemptive, and this approach also extends to entities layer. At first, you can place all your code in the model segment of your page (widget, feature), and then consider refactoring it later, when business requirements are stable.

Remember: the later you move code to the entities layer, the less dangerous your potential refactors will be โ€” code in Entities may affect functionality of any slice on higher layers.

2. Avoid Unnecessary Entitiesโ€‹

Do not create an entity for every piece of business logic. Instead, leverage types from shared/api and place logic in the model segment of a current slice. For reusable business logic, use the model segment within an entity slice while keeping data definitions in shared/api:

๐Ÿ“‚ entities
๐Ÿ“‚ order
๐Ÿ“„ index.ts
๐Ÿ“‚ model
๐Ÿ“„ apply-discount.ts // Business logic using OrderDto from shared/api
๐Ÿ“‚ shared
๐Ÿ“‚ api
๐Ÿ“„ index.ts
๐Ÿ“‚ endpoints
๐Ÿ“„ order.ts

3. Exclude CRUD Operations from Entitiesโ€‹

CRUD operations, while essential, often involve boilerplate code without significant business logic. Including them in the entities layer can clutter it and obscure meaningful code. Instead, place CRUD operations in shared/api:

๐Ÿ“‚ shared
๐Ÿ“‚ api
๐Ÿ“„ client.ts
๐Ÿ“„ index.ts
๐Ÿ“‚ endpoints
๐Ÿ“„ order.ts // Contains all order-related CRUD operations
๐Ÿ“„ products.ts
๐Ÿ“„ cart.ts

For complex CRUD operations (e.g., atomic updates, rollbacks, or transactions), evaluate whether the entities layer is appropriate, but use it with caution.

4. Store Authentication Data in sharedโ€‹

Prefer shared layer to creating a user entity for authentication data, such as tokens or user DTOs returned from the backend. These are context-specific and unlikely to be reused outside authentication scope:

  • Authentication responses (e.g., tokens or DTOs) often lack fields needed for broader reuse or vary by context (e.g., private vs. public user profiles).
  • Using entities for auth data can lead to cross-layer imports (e.g., entities into shared) or usage of @x notation, complicating the architecture.

Instead, store authentication-related data in shared/auth or shared/api:

๐Ÿ“‚ shared
๐Ÿ“‚ auth
๐Ÿ“„ use-auth.ts // authenticated user info or token
๐Ÿ“„ index.ts
๐Ÿ“‚ api
๐Ÿ“„ client.ts
๐Ÿ“„ index.ts
๐Ÿ“‚ endpoints
๐Ÿ“„ order.ts

For more details on implementing authentication, see the Authentication guide.

5. Minimize Cross-Importsโ€‹

FSD permits cross-imports via @x notation, but they can introduce technical issues like circular dependencies. To avoid this, design entities within isolated business contexts to eliminate the need for cross-imports:

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

An isolated context encapsulates all related logic (e.g., order items and customer info) within a single module, reducing complexity and preventing external modifications to tightly coupled logic.