Перейти к основному содержимому

Разбиение приложения

Group: Layers

Первый уровень разделения: по скоупу влияния модуля

Самопроверка

"К какому слою приложения относится модуль?"

└── src/
├── app/ # Инициализирующая логика приложения
├── processes/ # (Опц.) Процессы приложения, протекающие над страницами
├── pages/ # Страницы приложения
├── widgets/ # Самостоятельные и полноценные блоки для страниц
├── features/ # (Опц.) Обрабатываемые пользовательские сценарии
├── entities/ # (Опц.) Бизнес-сущности, которыми оперирует предметная область
└── shared/ # Переиспользуемые модули, без привязки к бизнес-логике

Порядок слоев

Если посмотреть на порядок слоев - то можно выделить две закономерности:

По уровню знания/ответственности

app > processes > pages > features > entities > shared

Модуль "знает" только про себя и нижележащие модули, но не вышележащие

Это же влияет и на разрешенные импорты

По уровню опасности изменений

shared > entities > features > pages > processes > app

Чем ниже расположен модуль - тем опаснее вносить в него изменения

Т.к. скорее всего он заиспользован во многих вышележащих слоях

Group: Slices

Второй уровень разделения: по конкретной функциональности БЛ

Методология почти не влияет на этот уровень и многое зависит от конкретного проекта

Самопроверка

"Какую область БЛ затрагивает модуль?"

До этого - надо определится со скоупом влияния (layer)

├── app/
| # Не имеет конкретных слайсов,
| # Т.к. там содержится мета-логика над проектом и его инициализации
├── processes/
| # Слайсы для реализации процессов на страницах
| ├── payment
| ├── auth
| ├── quick-tour
| └── ...
├── pages/
| # Слайсы для реализации страниц приложения
| # При этом, в силу специфики роутинга - могут вкладываться друг в друга
| ├── profile
| ├── sign-up
| ├── feed
| └── ...
├── widgets/
| # Слайсы для реализации самостоятельных блоков страниц
| ├── header
| ├── feed
| └── ...
├── features/
| # Слайсы для реализации пользовательских сценариев на страницах
| ├── auth-by-phone
| ├── inline-post
| └── ...
├── entities/
| # Слайсы бизнес-сущностей для реализации более сложной БЛ
| ├── viewer
| ├── posts
| ├── i18n
| └── ...
├── shared/
| # Не имеет конкретных слайсов
| # Представляет собой скорее набор общеиспользуемых сегментов, без привязки к БЛ

Правила

Поскольку слайс представляет собой конкретный уровень абстракции, то методология обязана наложить на него определенные правила

Low Coupling & High Cohesion

Слайсы одного слоя не могут использовать друг друга напрямую, а их взаимодействие и композиция должны определяться на более верхнем слое, относительно их текущего

features/baz/ui.tsx
// Плохо: фича импортит другую фичу (слайсы одного слоя)
import { Bar } from "features/bar"

function Baz({ foo, ...barProps}) {
...
<Bar {...barProps} />
}
pages/foo/ui.tsx
// Хорошо: фичи компонуются на странице (вышележащий слой)
import { Baz } from "features/baz"
import { Bar } from "features/bar"

function Foo() {
...
<Baz {...fooProps}>
<Bar {...barProps} />
</Baz>
}

Grouping

  • В большинстве случаев следует избегать вложенности в слайсах, а использовать лишь структурную группировку по папкам, без дополнительной связующей логики

    features/order/           # Группа фич
    ├── add-to-cart # Полноценная фича
    ├── total-info # Полноценная фича
    - ├── model.ts # Общая логика для группы
    - ├── hooks.ts # Общие хуки для группы
    └── index.ts # Публичный API с реэкспортом фич
  • При этом некоторые слои (например pages), изначально требуют вложенности из-за требований проекта / фреймворка

    pages/
    ├── order/
    | ├── cart/
    | ├── checkout/
    | | ├── delivery/
    | | └── payment/
    | ├── result/
    | └── index.tsx
    ├── auth/
    | ├── sign-in/
    | └── sign-up/
    ├── home/
    ├── catalog/
Важно

Следует по-максимуму избегать вложенных слайсов, но даже если приходится их использовать (например для pages) нужно связывать их явным образом, во избежание непредвиденных последствий

Group: Segments

Третий уровень разделения: по назначению модуля в коде и реализации

Самопроверка

"Какую часть тех. реализации логики затрагивает модуль?"

До этого - надо определится со скоупом влияния (слой) и доменной принадлежностью (слайсом)

{layer}/
├── {slice}/
| ├── ui/ # UI-логика (components, ui-widgets, ...)
| ├── model/ # Бизнес-логика (store, actions, effects, reducers, ...)
| ├── lib/ # Инфраструктурная логика (utils/helpers)
| ├── config*/ # Конфигурация (проекта / слайса)
| └── api*/ # Логика запросов к API (api instances, requests, ...)

При этом, каждый сегмент может быть представлен как в виде файла, так и в виде отдельной директории - в зависимости от сложности и размеров

Ограничения

Методология разрабатывалась с целью - не ограничивать и не утруждать разработчиков правилами выбора абстракций (хотелось, чтобы любой из сегментов можно было использовать в любом слое)

Однако в результате дискуссий и анализа обширного опыта - было определено, что лучше и практичнее ограничить каждый слой на используемые внутри сегменты.

Общие правила

  1. Чем выше расположен слой - тем больше он знает про БЛ приложения и наоборот
  2. API логику рекомендуется класть в shared, чтобы логика не распылялась по проекту
    • Как правило - она общая и представлена в виде единых инстансов
    • Edge-case "exceptions": GraphQL, react-query hooks

Применение для слоев

СлойСодержимоеРазрешенные сегменты
appНе включает в себя слайсы и содержит логику инициализацииИмеющиеся сегменты не совсем подходят, а потому используются обычно /providers (/hoc, ...), /styles и т.д. Очень зависит от проекта и вряд ли решается методологией
processesСлайсы внутри включают в себя только бизнес-логику, без отображения (1)lib model (api)
pagesСлайсы внутри включают в себя ui- и model- композицию различных фичей для конкретной страницыui lib model (api)
featuresСлайсы внутри включают в себя композицию сущностей и реализацию БЛ в модели + отображениеui lib model (api)
entitiesСлайсы внутри представляют разрозненный набор подмодулей для использованияui lib model (api)
sharedСодержит только инфраструктурную логику без БЛ (1)ui lib api

См. также