Platform
    • FSD Layers
    • Entity Structure
Projects
  • API Connections & Tools
  1. Documentation
  2. Documentation

Предметная область (Entities)

Сущности — это базовые бизнес-единицы проекта. Данный раздел описывает унифицированную структуру любой сущности, основанную на строгих архитектурных правилах.

Структура слоя Entities

Каждая бизнес-сущность внутри src/entities/* обязана следовать жестко-заданной директориальной структуре для изоляции логики и UI.

Файловая структура

Стандартные модули сущности

Полная сущность состоит из следующих обязательных модулей:

01/api

RTK Query эндпоинты сущности. Конвертеры вызываются внутри API-слоя: transformResponse мапит backend T*Backend в frontend-модели, а query — params/body через map*ToBackend. Хуки уже отдают domain-модели — UI не должен повторно мапить DTO.

Изучить архитектуру RTK Query
api/[entity-name].service.ts
1import { baseApi } from "@/shared/api/backend"; 2import type { IPaginationRequest, IPaginationResponse } from "@/shared/types"; 3import { 4 mapEntitiesQueryToBackend, 5 mapEntitiesToFrontend 6} from "../converters"; 7import type { TEntitiesBackend, IEntity } from "../types"; 8 9/** Converters run here — hooks return frontend models, not raw DTOs */ 10export const entityApi = baseApi.injectEndpoints({ 11 endpoints: (builder) => ({ 12 getEntities: builder.query< 13 IPaginationResponse<IEntity>, 14 IPaginationRequest 15 >({ 16 query: (filters) => ({ 17 url: "/entities", 18 params: mapEntitiesQueryToBackend(filters) 19 }), 20 transformResponse: (response: TEntitiesBackend) => 21 mapEntitiesToFrontend(response), 22 providesTags: [ENUM_API_TAGS.ENTITIES] 23 }) 24 }) 25});

02/converters

Мапперы данных. Превращают сырой DTO от сервера в удобную Domain Model. Это изолирует UI-компоненты от внезапных изменений контрактов бекенда.

Архитектура конвертеров
converters/[entity-name].converters.ts
1export const mapEntityToFrontend = (data: TEntityBackend): IEntity => ({ 2 id: data.id, 3 type: data.type as ENUM_ENTITY_TYPE, 4 dateCreated: formatDate(data.date_created), 5 client: data.client, 6 status: data.status as ENUM_ENTITY_STATUS 7}); 8 9export const mapEntitiesToFrontend = ( 10 response: TEntitiesBackend 11): IPaginationResponse<IEntity> => ({ 12 data: response.items.map(mapEntityToFrontend), 13 total: response.total_count 14}); 15 16export const mapEntityToBackend = ( 17 data: Partial<IEntityDetail> 18): Partial<TEntityDetailBackend> => ({ 19 id: data.id, 20 type: data.type, 21 date_created: data.dateCreated, 22 client: data.client, 23 status: data.status 24}); 25

03/handlers

Обработчики MSW (Mock Service Worker). Используются для перехвата сетевых вызовов во время разработки и в интеграционных тестах (Vitest / RTL).

Как работает MSW
handlers/[entity-name].handlers.ts
1import { HttpResponse } from "msw"; 2import { createMockHandler } from "@/shared/api/msw/utils"; 3import { listEntitiesBackend } from "../mock"; 4 5/** Backend DTO responses — converters run in RTK transformResponse */ 6export const entityHandlers = [ 7 createMockHandler( 8 { url: "/entities", method: "GET", _types: { body: undefined } }, 9 () => HttpResponse.json(listEntitiesBackend()) 10 ), 11 createMockHandler( 12 { url: "/entities/:id", method: "GET", _types: { body: undefined } }, 13 ({ params }) => { 14 const { items } = listEntitiesBackend(); 15 const entity = items.find((e) => e.id === params.id); 16 if (!entity) return new HttpResponse(null, { status: 404 }); 17 return HttpResponse.json(entity); 18 } 19 ) 20];

04/mock

Статические фикстуры и фейковые данные (mock data), используемые для эмуляции ответа сервера в связке с MSW handlers или для Storybook.

Раздел про MSW и мок-данные
mock/[entity-name].mock.ts
1import type { TEntityBackend } from "../types"; 2 3export const ENTITIES_MOCK: TEntityBackend[] = [ 4 { 5 id: "ENT-001", 6 type: "individual", 7 date_created: "2024-03-15T12:00:00Z", 8 client: "John Doe", 9 pax: 2, 10 status_code: "CONFIRMED" 11 } 12];

05/schema

Single Source of Truth для типов данных сущности. Содержит Zod-объекты со встроенной интернационализацией (i18n) ошибок.

Использование Zod
schema/[entity-name]-create.schema.ts
1import { z } from "zod"; 2import { i18nKey } from "@/shared/config"; 3// прямой путь к form.types — не barrel "../types" 4import { ENUM_FORM_ENTITY_CREATE } from "../types/entity-create-form.types"; 5 6const msg = i18nKey<TEntityKeys>(); 7const F = ENUM_FORM_ENTITY_CREATE; 8 9/** Runtime only — inferred form types belong in types/ (одна пара на операцию) */ 10export const ENTITY_CREATE_SCHEMA = z.object({ 11 [F.CLIENT]: z.string().min(1, { message: msg("errors.client.required") }), 12 [F.STATUS]: z.enum(["PENDING", "CONFIRMED"]), 13 [F.PAX]: z.number().min(1) 14});
types/[entity-name]-create-form.types.ts
1import type { FieldValues } from "react-hook-form"; 2import { z } from "zod"; 3// только import type — в рантайме зависимость schema → form.types односторонняя 4import type { ENTITY_CREATE_SCHEMA } from "../schema"; 5 6export const ENUM_FORM_ENTITY_CREATE = { 7 CLIENT: "client", 8 STATUS: "status", 9 PAX: "pax" 10} as const; 11 12export type ENUM_FORM_ENTITY_CREATE_TYPE = 13 (typeof ENUM_FORM_ENTITY_CREATE)[keyof typeof ENUM_FORM_ENTITY_CREATE]; 14 15export type TEntityCreateSchema = z.infer<typeof ENTITY_CREATE_SCHEMA> & 16 FieldValues;

06/slice

Redux-стейт сущности (если она хранит глобальные данные). Часто здесь же лежат селекторы и экшены. Важно: стейт должен зависеть только от Domain Model, а не от DTO.

slice/[entity-name].slice.ts
1import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 3interface IEntitySliceState { 4 selectedId: string | null; 5} 6 7const initialState: IEntitySliceState = { 8 selectedId: null 9}; 10 11export const entitySlice = createSlice({ 12 name: "entity", 13 initialState, 14 reducers: { 15 setSelectedEntity: (state, action: PayloadAction<string>) => { 16 state.selectedId = action.payload; 17 } 18 } 19});

07/ui

Dump-компоненты или Smart-компоненты уровня сущности. Например: UserCard, UserAvatar. Могут быть переиспользованы в разных фичах и виджетах.

ui/[entity-name]-card.tsx
1interface IEntityCardProps { 2 entity: IEntity; 3 onSelect: (id: string) => void; 4} 5 6export const EntityCard = ({ entity, onSelect }: IEntityCardProps) => ( 7 <Card onClick={() => onSelect(entity.id)}> 8 <CardHeader>{entity.client}</CardHeader> 9 <CardContent>Pax: {entity.pax}</CardContent> 10 </Card> 11);

08/types

Глобальные TS-интерфейсы и типы для конкретной сущности (например, интерфейс Domain Model, если Zod не покрывает всю логику или требуется выделить часть типов). Рекомендуется использовать выведение типов из Zod-схем, но если это невозможно, типы описываются здесь.

Выведение типов в Zod
types/[entity-name].interface.ts
1import type { IPaginationRequest, IPaginationResponse } from "@/shared/types"; 2 3export interface IEntityDates { 4 from: string; 5 to: string; 6} 7 8export interface IEntity { 9 id: string; 10 type: ENUM_ENTITY_TYPE; 11 dateCreated: string; 12 client: string; 13 pax: number; 14 dates: IEntityDates; 15 status: ENUM_ENTITY_STATUS; 16} 17 18export type TEntitiesPage = IPaginationResponse<IEntity>; 19export interface IEntitiesQuery extends IPaginationRequest {} 20