Skip to content

docs: add guidance on when to create an entity#898

Open
dyakubovskiy wants to merge 8 commits intomainfrom
feature/update_entities_info
Open

docs: add guidance on when to create an entity#898
dyakubovskiy wants to merge 8 commits intomainfrom
feature/update_entities_info

Conversation

@dyakubovskiy
Copy link

Background

Русский:
Добавляет практические рекомендации по выделению сущностей (entities) на этапе масштабирования проекта. Рекомендации помогают избежать распространённых ошибок при проектировании архитектуры: преждевременного выделения сущностей или, наоборот, запоздалого рефакторинга.

English:
Adds practical recommendations for extracting entities during project scaling. The guidelines help avoid common architectural pitfalls: premature entity extraction or, conversely, delayed refactoring when the codebase has already become entangled.

Changelog

Русский:

  1. Добавлены рекомендации на русском языке о критериях выделения сущностей при масштабировании
  2. Добавлен перевод рекомендаций на английский язык
  3. Описаны признаки для выделения отдельной сущности: стабильность доменной области, повторное использование в разных слоях, рост сложности бизнес-логики

English:

  1. Added Russian-language recommendations on entity extraction criteria during scaling
  2. Provided English translation of the recommendations
  3. Described indicators for extracting a separate entity: domain stability, reuse across layers, and growing complexity of business logic

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

built with Refined Cloudflare Pages Action

⚡ Cloudflare Pages Deployment

Name Status Preview Last Commit
pr-fsd ✅ Ready (View Log) Visit Preview af99ee8

Copy link
Member

@illright illright left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The content is great, thank you! I agree with @Gaic4o that the diagram should perhaps be inserted as an image because it is unreadable when rendered by Docusaurus:

Image

And I also think that the Reference section is not the best place to put advice. This documentation is structured by the Diataxis framework (https://diataxis.fr/), and we've been keeping advice mostly in Guides. Speaking of which, there is a guide about Excessive entities (Code smells & issues), and I have a feeling that this new section shares a lot of content with that guide, could we perhaps deduplicate and leave a link instead?

Copy link
Author

@dyakubovskiy dyakubovskiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replaced the diagram with a Mermaid

Copy link
Author

@dyakubovskiy dyakubovskiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replaced the diagram with a Mermaid one

@Stanislavsky69
Copy link

[RU]

Рекомендации по выявлению сущностей.

Сущность - объект предметной области, который отражает суть бизнеса. Отличительная черта сущности - его уникальность. При проектировании слоя entities в рамках FSD нужно сначала определиться с бизнесом, что есть сущность по его уникальности. Например, уникальный атрибут у заказа - номер, у товара - артикул. Иногда, есть данные, которых не хватает для однозначного определения сущности. Тогда нужно изучить структуру, которую отдает бэк и найти уникальный атрибут, например, id. Если и этого не хватает для однозначного определения сущности, то можно подумать об объекте в реально жизни. Может ли он обладать своей уникальностью?

Слой entities отражает ваши основные объекты и нуждается в проектировании на ранних этапах.

Пример:

Типичная задача.

Заголовок:
Реализовать фичу добавления пользователя в систему

Описание:

  1. Обязательное поле ФИО
  2. Обязательное поле Адрес проживания (город, улица, дом)
  3. Обязательное поле год рождения
  4. Новому пользователю назначается роль "менеджер" по умолчанию.

Первый пункт. Может ли ФИО говорить об уникальности? Нет.
Второй пункт. Может ли адрес проживания говорить об уникальности? Я бы сказал, что он может являться уникальным, но не гарантировать уникальность. Обратите внимание, что в требованиях нет индекса.
Третий пункт. Год рождения всегда не уникальный.
Четвертый пункт. У роли есть имя и набор доступов. У роли нет явных бизнес ориентированных атрибутов указывающих на ее уникальность. А раз нет, то мы
смотрим на технические атрибуты. У роли есть id и это значение инкрементное. Вывод: роль - сущность.

А что с пользователем? Если по бизнесу нет явных атрибутов указывающих на ее уникальность, а их нет, то мы смотрим на технические атрибуты. У пользователя как и у роли есть атрибут id. Вывод: пользователь - сущность.

Другой пример

Заголовок:
Реализовать возможность делится сформированным отчетом

Описание:

  1. После создания отчета показывать уникальный ключ (хэш) в модальном окне
  2. Добавить описание в модальное окно: "Ключ живет 30 минут. Вы можете поделится им с другим пользователем, чтобы он мог просмотреть отчет."

Второй пункт нас не интересует. Первый пункт нам говорит о том, что у отчета есть уникальный ключ, который характеризует его как сущность.

Не бойтесь общаться с бизнесом. Слой entities должен изменятся именно с изменениями в бизнесе.

Я рекомендую нарезать сущности сразу, чтобы избежать анемичной модели (https://en.wikipedia.org/wiki/Anemic_domain_model).

Важно! Это противоречит подходу с page first, потому что требует чуть больше времени и усилий.

[EN]

Recommendations for identifying entities.

An entity is a domain object that reflects the essence of a business. The distinguishing feature of an entity is its uniqueness. When designing the entities layer within the FSD framework, it is necessary to first determine the business and identify the entity based on its uniqueness. For example, the unique attribute of an order is its number, while the unique attribute of a product is its SKU. In some cases, there may be data that is insufficient to uniquely identify an entity. In such cases, it is necessary to examine the structure provided by the back-end and identify a unique attribute, such as an ID. If even this is insufficient to uniquely identify an entity, it may be necessary to consider the entity in real life and determine whether it possesses its own uniqueness.

The entities layer represents your primary objects and requires early design.

Example:

This is a typical task.

Title:
Implement the feature of adding a user to the system

Description:

  1. Required field: Full Name
  2. Required field: Address (city, street, house)
  3. Required field: Year of Birth
  4. The new user is assigned the "manager" role by default.

First point: Can the Full Name be considered unique? No.
Second point. Can the residential address speak about uniqueness? I would say that it can be unique, but it does not guarantee uniqueness. Please note that there is no index in the requirements.
Third point. The year of birth is always not unique.
Fourth point. The role has a name and a set of permissions. The role does not have any explicit business-oriented attributes that indicate its uniqueness. Since there are no such attributes, we look at the technical attributes. The role has an id, and this value is incremental. Therefore, the role is an entity.

What about the user? If there are no explicit business-oriented attributes that indicate its uniqueness, and there are none, then we look at the technical attributes. The user, like the role, has an id attribute. Therefore, the user is an entity.

Another example

Title:
Implement the ability to share a generated report

Description:

  1. After creating a report, show the unique key (hash) in a modal window.
  2. Add a description to the modal window: "The key is valid for 30 minutes. You can share it with another user so they can view the report."

We are not interested in the second point. The first point tells us that the report has a unique key that characterizes it as an entity.

Don't be afraid to communicate with the business. The entities layer should change with changes in the business.

I recommend slicing entities immediately to avoid an anemic model (https://en.wikipedia.org/wiki/Anemic_domain_model).

Important! This goes against the page first approach because it requires a little more time and effort.

@illright
Copy link
Member

I still think this page should not be in the Reference, but rather, in Guides. It gives advice rather than describing definitions and base principles, as the reference should, in my opinion.

Copy link
Contributor

@Solant Solant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got a read, I see good points how we can show proper usage of entities, but I am a bit concerned about a current state of this article:

  • it is quite large and repeats itself a lot (in regards to business-management communication)
  • examples are vue-specific, and it would be much better to show how to handle business logic in pure js (without react state and vue reactivity), as it might be hard for React developers to follow Vue example and vice-versa
  • while text states that there should be a good reason to create an entity, in the examples there are couple of entities created straight from the DTO type, without any abstraction. so it looks like examples are trying to create an entity when it is not required (just for the sake of it), which can lead to leaky abstractions
  • some paragraphs are repeating already existing pages from the documentation

@Gaic4o Gaic4o changed the title Feature/update entities info docs: add guidance on when to create an entity Feb 13, 2026
@Gaic4o
Copy link
Contributor

Gaic4o commented Feb 13, 2026

One concern about the entities/user example:

In real-world products, “User” is often a very broad umbrella concept, so readers might interpret this page as “put all user-related business logic into entities/user/api and entities/user/model.” Over time, this can turn entities/user into a dumping ground where more and more code accumulates, which may hurt maintainability.

With that in mind, I wanted to ask (carefully) whether there might be a better example than User, Customer, Order, Product etc.. for this section — or perhaps a better way to phrase it to avoid that interpretation.

Also, in larger domains, it’s common to either:

(a) modularize within the same slice by responsibility (e.g., splitting read/write operations and separating model concerns), for example:

entities/user/
  api/
    queries/      # read operations
    mutations/    # write operations
  model/
    permissions/
    ...

(b) or split by domain context into more specific entities or slice groups. For example:

entities/user/
  guide/
  profile/

It might be helpful to mention these evolution paths so readers don’t assume a single giant entities/user is always the intended direction.

Looking at the current example code, it seems like most of it follows a structure like this:

shared/
  api/
    client.ts         # infrastructure only
    types.ts

entities/
  user/
    api/
      user-api.ts     # API + mapping (~100 lines)
    model/
      types.ts
      use-user-permissions.ts  # business logic (~150 lines)
    index.ts

So I brought this up after thinking about it in that context.

@dyakubovskiy
Copy link
Author

Taken into account and implemented in the new version. The file has been fully rewritten and moved from the reference section to guides — examples are now in React+TS and plain TypeScript without framework reactivity, repetitions are removed, the Approach A section is condensed with a link to the existing api-requests page, and architectural issues are fixed (entity type moved from api/ to model/, files renamed to reflect their contents).

Copy link
Contributor

@Solant Solant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the flow is a bit fragmented: some paragraphs have no text except the small code samples, and lists consisting of minimal synopses might not be enough for the reader to understand the idea behind the reasoning. So I suggest adding some reasoning/explanation

Comment on lines +154 to +168
export interface UserProfile {
id: string
name: string
email: string
joinedDaysAgo: number
}

function mapProfile(dto: UserProfileDTO): UserProfile {
return {
id: String(dto.user_id),
name: dto.full_name,
email: dto.email,
joinedDaysAgo: dto.joined_days_ago,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the UserProfile type as a 1:1 mapping of the existing DTO? I think it can introduce unnecessary friction if some changes are required: instead of changing one UserProfileDTO, we have to change UserProfile and mapper every time the response changes.

- Unknown what other fields will be needed elsewhere
- YAGNI — don't create structure "for the future"

### Triggers for Moving to Approach A or B
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe it is better to clarify what approaches A and B are, because the reader might forget what options A and B were, for example

"Triggers to move code to shared/api or entities"

- Teams starting to work with FSD
- Small projects (fewer than ~10 screens)
- Projects with frequently changing business logic
- When it's unclear which entities have stabilized
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is also a practical approach when generators like orval or openapi are used


// Approach A: ~15 files need updates
// Approach B: 1 file (the mapper in entities/user/api/)
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a good way of using mappers. In this particular example (when the field is renamed), the change is indeed localized, but usually backend changes are different (like field addition/removal), and mappers cannot prevent cascade changes when some data is missing or a new field is added

- Миграция в `entities/` при усложнении

**Подход Б: Доменное API** (`entities/*/api/`)
- API привязано к бизнес-сущности

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я бы предложил использовать API как доказательство со звездочкой, но не основной идеей. Здесь валидным определением будет все-таки понимание бизнеса и общение с представителями бизнеса. Если бизнес объект является уникальным по своему уникальному свойству (id, article, uid и т.д), то это можно однозначно интерпретировать как сущность, даже не зная будущих фичей.


// Техническая уникальность
User { id: "uuid-123" }
Payment { id: 456 }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Payment нужно отнести к бизнесу. Его уникальный атрибут будет в этом случае его тип, например, cash или card

Payment { id: 456 }
```

Наличие уникального идентификатора **не означает** автоматического создания Entity. Это лишь признак того, что объект может ею стать.
Copy link

@Stanislavsky69 Stanislavsky69 Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тогда нужен пример, когда бизнес объект не может быть сущностью, либо убрать это замечание.


```typescript
// Бизнес-термины (потенциальные сущности)
User, Customer, Order, Product, Invoice, Payment, Subscription

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я бы добавил как бизнес называет это своими словами и как это может выглядеть в коде. Сейчас это уже конкретные интерпретации в коде

```
Order -> belongs to -> User
Order -> contains -> Products
User -> has -> Subscription

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мб добавить на типах вариант?

joinedDaysAgo: number
}

function mapProfile(dto: UserProfileDTO): UserProfile {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProfileModel. Ведь мы получим модель для приложения


Создавайте `entities/*/model/` — независимо от того, где лежит API (`shared/api` или `entities/*/api/`) — когда появляется любое из следующего:

### 1. Агрегация данных

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Как-будто нужно дополнить, что это создание модели из нескольких источников.

}
```

### 2. Бизнес-правила и инварианты

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Предлагаю убрать "инварианты", потому что применения этого термина здесь нет. Инвариант - валидный объект согласно бизнес требованиям. Если проще, то должны быть проверки важных атрибутов модели, без которых модель считается не валидной. Пример: Пользователь не может быть без имени или Заказ без номера заказа. Эти проверки происходят за одну операцию/транзакцию над моделью (агрегатом)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants