0002. Модульный монолит: разделение src/core и src/modules
Копия ADR из репозитория:
backend/docs/adr/0002-modular-monolith-layout.md. Канонический текст — в репозитории; портал обновляется командойnpm run sync:adr.
- Статус: accepted
- Дата: 2026-05-28
- Связанные задачи: OP-24 (F1 · Платформа и каркас приложения)
Контекст и проблема
ТЗ (9.1) задаёт архитектуру «модульный монолит на NestJS». Нужно зафиксировать конкретную раскладку каталогов и правила взаимодействия модулей до того, как команды начнут параллельно делать фичи (5.1–5.15) — иначе фичи будут лезть друг к другу за хранилища и конфиг разными способами, и переход к раздельным сервисам позже (если потребуется) окажется неподъёмным.
Рассмотренные варианты
- Плоская структура
src/<feature>— все фичи и инфраструктура на одном уровне. Просто, но смешивает горизонтальные сервисы (конфиг, БД, Redis) с доменными модулями и стирает границу «инфраструктура / бизнес-логика». src/core(инфраструктура) +src/modules(фичи) — два уровня:coreсодержит только горизонтальные@Global()-модули,modules— домен. Чёткая граница: что вcore, то можно инжектить везде без импорта; фичи не зависят друг от друга напрямую.apps/+libs/(NestJS-монорепо) — встроенное разделение NestJS на приложения и библиотеки. Избыточно для одного приложения; усложняет сборку и навигацию без выгоды на текущем масштабе.
Решение
Принят вариант 2: src/core/ + src/modules/.
src/core/<service>/— только горизонтальные сервисы (конфиг, доступ к хранилищам, общие провайдеры). Объявляются@Global(); экспортируют DI-токены (DataSourceиз@nestjs/typeorm,REDIS_CLIENT) и фасадные сервисы (AppConfigService).src/modules/<feature>/— фичи-модули 5.1–5.15 из ТЗ. Внутри —*.module.ts,*.controller.ts,*.service.ts,dto/,entities/,*.spec.ts.- Доступ к окружению — только через
AppConfigService. Прямое чтениеprocess.envзапрещено. - Доступ к PostgreSQL — через TypeORM (
@InjectRepository(Entity)послеTypeOrmModule.forFeature([...]), либо@InjectDataSource()для сырых запросов и транзакций; см. ADR-0001, OP-37); к Redis — через@Inject(REDIS_CLIENT)(ioredis).
Последствия
- Положительные: одна и та же раскладка во всех фичах; горизонтальные сервисы поднимаются один раз и реюзаются через DI; легко найти, где живёт что; пред-условие для будущей миграции отдельных модулей в самостоятельные сервисы (если/когда потребуется).
- Отрицательные / компромиссы: дополнительный слой каталогов, чуть больше
церемонии при создании новой фичи; нужен контроль ревью, чтобы инфра-код не
утекал в
modules/. - Дальше: конвенция зафиксирована в
src/modules/README.mdиbackend/README.md. При выполнении OP-25 (health-чеки) и OP-37 (ORM) — не ломать разделение: ORM-модуль регистрируется вcore/database/, health-чеки читаютDataSource/REDIS_CLIENTчерез DI.