Files
docs/canons/style-guide/slm-design/architecture/monorepo.md

236 lines
13 KiB
Markdown
Raw Normal View History

---
title: Монорепозитории
description: "Раздел описывает, как применять SLM Design, когда фронтенд-проекты находятся в одном монорепозитории. В нём показано, что остаётся внутри приложений, что можно выносить в `packages/` и какие ограничения действуют для общих пакетов."
---
# Монорепозитории
Раздел описывает, как применять SLM Design, когда фронтенд-проекты находятся в одном монорепозитории. В нём показано, что остаётся внутри приложений, что можно выносить в `packages/` и какие ограничения действуют для общих пакетов.
## Определение
**Монорепозиторий — внешний уровень организации нескольких фронтенд-приложений и общих пакетов. SLM применяется внутри каждого приложения, а frontend-пакеты, относящиеся к SLM, содержат переиспользуемый код, вынесенный из слоёв `ui`, `infra` и `shared`.**
## Базовая структура
Каждое приложение внутри `apps/` сохраняет собственную SLM-структуру в `src/`.
```text
repo/
├── apps/
│ ├── web/
│ │ └── src/
│ │ ├── app/
│ │ ├── layouts/
│ │ ├── screens/
│ │ ├── widgets/
│ │ ├── business/
│ │ ├── infra/
│ │ ├── ui/
│ │ └── shared/
│ └── admin/
│ └── src/
│ └── ...
└── packages/
├── ui/
│ ├── button/ # самостоятельный пакет UI-модуля
│ ├── input/ # самостоятельный пакет UI-модуля
│ └── modal/ # самостоятельный пакет UI-модуля
├── infra/
│ ├── theme/ # самостоятельный пакет infra-модуля
│ ├── backend-api/ # самостоятельный пакет infra-модуля
│ └── logger/ # самостоятельный пакет infra-модуля
└── shared/ # единый shared-пакет
├── package.json
└── src/
├── lib/ # переиспользуемые утилиты
├── helpers/ # переиспользуемые helpers
└── index.ts
```
`apps/{app}/src` — граница SLM-приложения. `packages/*` находятся выше SLM и не добавляют новые архитектурные слои.
## Группировка frontend-пакетов
Frontend-пакеты, вынесенные из SLM-приложений, рекомендуется группировать по источнику кода: `ui`, `infra`, `shared`.
```text
packages/ui/* # пакеты UI-модулей
packages/infra/* # пакеты infra-модулей
packages/shared # единый shared-пакет
```
Эта группировка повторяет названия SLM-слоёв для навигации, но сама не является слоистой архитектурой внутри `packages/`. Монорепозиторий может содержать другие пакеты: tooling, конфиги, SDK, схемы, e2e и другие технические пакеты вне SLM.
## Пакет и модуль
Пакет не равен SLM-модулю: модуль — архитектурная единица внутри слоя приложения, package — единица монорепозитория для переиспользования, владения, сборки и публикации.
В `packages/ui/*` размещаются пакеты самостоятельных UI-модулей. В `packages/infra/*` размещаются пакеты самостоятельных инфраструктурных модулей. `packages/shared` устроен иначе: это единый пакет для переиспользуемых утилит, helpers и другого фундаментального кода без привязки к конкретному приложению.
```text
packages/ui/button/
packages/ui/modal/
packages/infra/theme/
packages/infra/backend-api/
packages/shared/
```
## Что остаётся в приложении
Слои `app`, `layouts`, `screens`, `widgets` и `business` остаются внутри конкретного приложения.
```text
apps/web/src/app/
apps/web/src/layouts/
apps/web/src/screens/
apps/web/src/widgets/
apps/web/src/business/
```
`app`, `layouts` и `screens` привязаны к роутингу, каркасу и страницам конкретного приложения. `widgets` не выносятся в пакеты, потому что это слой композиции интерфейса приложения.
`business` не выносится в `packages/*`. Домены остаются рядом со сценариями приложения, чтобы не превращать монорепозиторий в общий бизнес-слой.
## Что можно выносить
В пакеты выносится только код из `ui`, `infra` и `shared`, который потенциально будет использоваться в двух и более фронтенд-приложениях монорепозитория.
| Группа | Что выносить | Пример |
|--------|--------------|--------|
| `packages/ui/*` | Самостоятельные UI-модули без бизнес-логики | `packages/ui/button` |
| `packages/infra/*` | Самостоятельные технические сервисы | `packages/infra/backend-api` |
| `packages/shared` | Общие утилиты, helpers и фундаментальный код | `packages/shared` |
Пакет можно создавать сразу, если модуль имеет общую природу и ожидается его переиспользование между приложениями. App-specific код остаётся внутри приложения.
## UI-пакеты
В `packages/ui/*` размещаются переиспользуемые UI-модули.
```text
packages/ui/button/
├── package.json
└── src/
├── button.tsx
├── styles/
├── types/
└── index.ts
```
UI-пакет не содержит бизнес-логику, обращения к API, сценарные хуки приложения и композицию страниц.
## Infra-пакеты
В `packages/infra/*` размещаются переиспользуемые инфраструктурные модули.
```text
packages/infra/backend-api/
├── package.json
└── src/
├── clients/
├── config/
├── types/
└── index.ts
```
Привязанные к конкретному приложению сервисы остаются в `apps/{app}/src/infra`. Например, локализация со словарями конкретного продукта остаётся в приложении; общим пакетом может быть только переиспользуемый i18n-движок.
## Shared-пакет
`packages/shared` является единым пакетом.
```text
packages/shared/
├── package.json
└── src/
├── lib/
├── helpers/
└── index.ts
```
В `packages/shared` сразу выносится общий фундаментальный код: чистые функции, helpers, утилиты, независимые константы и другой код без знания о продукте.
Проектные стили, типы приложения, продуктовые конфиги и ресурсы, завязанные на одно приложение, в общий `shared` не выносятся.
## Имена пакетов и импорты
Путь импорта задаётся `name` в `package.json`, а не расположением директории.
```json
{
"name": "@repo/theme"
}
```
```text
packages/infra/theme/package.json
```
```ts
import { ThemeProvider } from '@repo/theme'
```
Пакеты должны импортироваться только через публичный API. Deep imports внутрь пакета запрещены.
```ts
// Хорошо
import { Button } from '@repo/button'
// Плохо
import { Button } from '@repo/button/src/button'
```
## Зависимости
На уровне монорепозитория приложения зависят от пакетов, а пакеты не зависят от приложений.
```text
apps → packages
packages -/→ apps
```
Внутри приложения продолжает действовать обычное направление зависимостей SLM.
```text
app → [ layouts | screens ] → widgets → business → infra → ui → shared
```
Пакеты не должны нарушать природу своей группы: `packages/ui/*` не импортирует `packages/infra/*`, `packages/shared` не импортирует другие группы, а `packages/infra/*` не знает о приложениях.
## Когда не выносить
Не выносите код в пакет, если он не может быть использован в двух и более фронтенд-приложениях, зависит от роутинга или страниц, содержит бизнес-логику, отражает продуктовую композицию конкретного интерфейса или не имеет стабильного публичного API.
Фактическое использование в одном приложении не запрещает пакет, если модуль имеет общую природу и потенциально нужен нескольким приложениям.
```text
# Плохо
apps/web/src/screens/home/parts/promo-section/
packages/ui/promo-section/
```
Если блок нужен только одной странице или отражает продуктовую композицию конкретного приложения, он остаётся локальным `parts/`-модулем.
## Конфигурационные пакеты
Конфигурационные пакеты не относятся к SLM-архитектуре.
Если в монорепозитории есть общие настройки TypeScript, ESLint, сборки или форматирования, они относятся к tooling-инфраструктуре репозитория. Такие пакеты могут находиться в `packages/`, но их структура зависит от выбранного инструментария и не участвует в правилах слоёв внутри `src/`.
## Правила
- SLM применяется внутри каждого `apps/{app}/src`.
- Frontend-пакеты, вынесенные из SLM-приложений, группируются в `packages/ui`, `packages/infra`, `packages/shared`.
- Группы `packages/ui`, `packages/infra`, `packages/shared` не являются SLM-слоями.
- В `packages/ui/*` размещаются пакеты самостоятельных UI-модулей.
- В `packages/infra/*` размещаются пакеты самостоятельных инфраструктурных модулей.
- `packages/shared` является единым пакетом для переиспользуемых утилит и helpers.
- Модуль можно размещать в пакете, если он потенциально будет использоваться в двух и более фронтенд-приложениях.
- `business`, `app`, `layouts`, `screens`, `widgets` не выносятся в пакеты.
- Проектные стили, типы приложения и продуктовые конфиги не выносятся в `packages/shared`.
- Пакеты не импортируют приложения.
- Межпакетные импорты идут только через публичный API.
- Deep imports внутрь пакетов запрещены.
- Локальная колокация важнее преждевременного выноса в `packages/*`.