Разбиение приложения
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
Слайсы одного слоя не могут использовать друг друга напрямую, а их взаимодействие и композиция должны определяться на более верхнем слое, относительно их текущего
// Плохо: фича импортит другую фичу (слайсы одного слоя)
import { Bar } from "features/bar"
function Baz({ foo, ...barProps}) {
...
<Bar {...barProps} />
}
// Хорошо: фичи компонуются на странице (вышележащий слой)
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, ...)
При этом, каждый сегмент может быть представлен как в виде файла, так и в виде отдельной директории - в зависимости от сложности и размеров
Ограничения
Методология разрабатывалась с целью - не ограничивать и не утруждать разработчиков правилами выбора абстракций (хотелось, чтобы любой из сегментов можно было использовать в любом слое)
Однако в результате дискуссий и анализа обширного опыта - было определено, что лучше и практичнее ограничить каждый слой на используемые внутри сегменты.
Общие правила
- Чем выше расположен слой - тем больше он знает про БЛ приложения и наоборот
- 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 |