Backend Typescript 1.0.0 Help

Монолитная архитектура

Что такое монолит

Монолитное приложение — это единый развёртываемый артефакт (один процесс, одна кодовая база и, как правило, один пакет поставки), в котором все подсистемы (интерфейс, доменная логика, интеграции, доступ к данным) размещены вместе. Монолит может быть примитивным «всё-под-одной-крышей» или дисциплинированным модульным монолитом с чёткими границами модулей.

Сильные стороны

  • Простота разработки и отладки. Одна кодовая база, единый рантайм и стек. Локальный запуск полностью имитирует продакшн-контекст.

  • Согласованность данных по умолчанию. Границы транзакций легко провести в рамках одной БД, проще реализовать инварианты.

  • Простая поставка. Один артефакт — меньше движущихся частей в CI/CD, стандартные стратегии релизов (blue/green, canary) применяются тривиально.

  • Более низкие накладные расходы. Нет межсервисной сетевой латентности и сериализации — меньше расходов на инфраструктуру и эксплуатацию.

Слабые стороны

  • Риск эрозии границ. При слабой дисциплине модули «протекают», образуя «большой ком грязи».

  • Масштабирование целиком. Горизонтальное масштабирование зачастую вынуждено тащить весь код, даже если горячая только малая часть.

  • Замедление сборки и тестов. Рост кодовой базы увеличивает время сборки, запуска тестов и обратной связи.

  • Сложности параллельной работы больших команд. Высокие риски конфликтов при мерджах и каскадных изменений.

Основные подходы к проектированию монолита

Слоистая архитектура (Layered)

Классическая стратификация: PresentationApplicationDomainInfrastructure. Слои формируют «вертикальные правила зависимости»: верхние зависят от нижних, но не наоборот.

Модульный монолит (Modular Monolith)

Монолит разделён на изолированные модули с публичными контрактами. Внутренности модулей закрыты, межмодульная коммуникация идёт через фасады, команды/запросы или доменные события (в памяти).

Вертикальные срезы (Feature Slices)

Структурирование по фичам/поддоменам: каждая фича содержит свой ui, application, domain, infrastructure. Минимизируется кросс-зависимость между фичами, команды работают автономно.

Чистая архитектура / Гексагональная архитектура

Домен в центре, вокруг — порты/адаптеры. Инфраструктура подключается «снаружи» и подменяема. Это усиливает тестируемость и независимость домена.

Ключевые архитектурные решения в монолите

  • Границы модулей. Определяйте инварианты и агрегаты. Что должно меняться транзакционно вместе — в одном модуле.

  • Политика транзакций. Явно описывайте границы ACID-согласованности. Сквозные транзакции через всё приложение — антипаттерн.

  • Доступ к данным. Избегайте «общей» БД как интеграционного слоя: модуль не должен читать таблицы другого модуля напрямую.

  • Коммуникация модулей. Команды/запросы и внутрипроцессные события лучше прямых импортов. Это снижает сцепление.

  • Наблюдаемость. Трейсы на уровень модулей/юзкейсов, метрики по горячим путям, структурированные логи.

  • Эволюция. План миграций схемы, фича-флаги, «двойная запись» при рефакторинге.

Использование КОП для разбиения разработки на команды

КОП (Компонентно-ориентированное программирование) — дисциплина проектирования, где система строится из независимых компонентов с чёткими интерфейсами и контрактами. В монолите это обеспечивает командную автономию без разделения на микросервисы.

  • Компонент → команда. Каждому компоненту назначается компонентная команда (ownership кода, сборки, инцидентов).

  • Контракты вместо общих утилит. Публичная поверхность компонента — фасады/порты; общий «утилитарный» слой — только для кросс-срезов (логирование, авторизация).

  • Изоляция хранилища. Таблицы компонента отделены (схема/префикс). Доступ — только через порт компонента.

  • Внутренние события. Компоненты публикуют доменные события (в памяти) и подписываются на них — без прямых импортов реализаций.

  • Границы изменения. Изменение инварианта одного компонента не должно требовать каскада в остальных.

export type ChargeResult = { ok: boolean; txId?: string }; export interface PaymentsPort { charge(orderId: string, amount: number): Promise<ChargeResult>; } export class OrdersService { constructor(private readonly payments: PaymentsPort) { } async place(orderId: string, amount: number) { const res = await this.payments.charge(orderId, amount); console.log(res.ok); // true return res; } }

Практики для монолита в продакшне

  • Статические правила импорта. Линт-правила/арх-линтеры запрещают пересечение модульных границ.

  • Контрактные тесты. Тесты проверяют публичные фасады модулей и события, а не приватные детали.

  • Миграции с обратной совместимостью. Схема меняется в несколько шагов: расширение → двойная запись → переключение → очистка.

  • Версионирование контрактов. Переход через v1/v2 фасадов, период совместимости.

  • Стратегии развертывания. Blue/Green, Canary, быстрый откат. Единый артефакт упрощает управление риском.

Типичные анти-паттерны и как их избегать

  • Big Ball of Mud. Лечится модульными границами, контрактами и архитектурным линкующим контролем.

  • Общая БД как интеграционный слой. Вместо прямого чтения чужих таблиц — порт/события.

  • God Object / God Service. Делите по инвариантам и агрегатам, а не по слоям контроллер/сервис/репозиторий механически.

  • Сквозные транзакции. Явно обозначайте границы. Межмодульные операции — через «сага»-подобные процессы внутри одного процесса.

Масштабирование и эволюция монолита

  • Scale-up & горизонтальное масштабирование процесса. Реплики приложения за балансировщиком, sticky-sessions при необходимости.

  • Разделение нагрузки. Вынесение фоновых работ в очереди/воркеры, read-replicas для отчётности.

  • Стратегия «Strangler Fig». Когда появится потребность — выносите самый горячий компонент в отдельный сервис, не ломая контракты.

Мини-шаблоны структуры репозитория (пример)

. ├─ app/ │ ├─ modules/ │ │ ├─ orders/ │ │ │ ├─ application/ │ │ │ ├─ domain/ │ │ │ └─ infrastructure/ │ │ └─ payments/ │ │ ├─ application/ │ │ ├─ domain/ │ │ └─ infrastructure/ │ ├─ shared/ # только кросс-срез: логирование, auth, tracing │ └─ platform/ # запуск, конфиг, DI-контейнер └─ tests/

Пример инварианта на уровне БД

Даже в монолите инварианты лучше дублировать: в домене и в БД.

ALTER TABLE orders ADD CONSTRAINT chk_total_non_negative CHECK (total_amount >= 0);

Чек-лист готовности

  • Определены публичные фасады модулей; приватное не импортируется снаружи.

  • Покрыты контрактные тесты и тесты инвариантов.

  • Наблюдаемость: метрики по модулю и ключевым юзкейсам, трассировка.

  • Схема БД разделена по модулям (схемы/префиксы), миграции обратносуместимы.

  • Релизная стратегия документирована, откат проверен.

Last modified: 01 October 2025