Чистый монолит лучше микросервисов: семь аргументов
BackendService Lab.
Микросервисы давно стали стандартным ответом на все архитектурные вопросы: команда растёт, продукт усложняется, значит пора дробить систему на десятки сервисов. На практике такой шаг часто добавляет больше операционной сложности, чем пользы. Для большинства продуктов чистый модульный монолит остаётся более рациональным выбором: он быстрее в разработке, проще в сопровождении и дешевле в эксплуатации.
Важно различать монолит как форму деплоя и монолит как хаос в коде. Плохой монолит держится на глобальном состоянии, скрытых зависимостях и бесконечных правках в одних и тех же файлах. Чистый монолит устроен иначе: домен разделён на модули, контракты между ними явные, инфраструктурный код изолирован, а изменения проходят через тесты и review.
1. Одна модель данных снижает количество ошибок
В микросервисной архитектуре данные быстро расползаются по нескольким базам, очередям и кэшам. Появляются eventual consistency, дублирование справочников, миграции контрактов и отдельные правила восстановления после сбоев. Это оправдано, когда бизнес действительно требует автономных контуров. Но если продуктовая модель ещё меняется каждую неделю, единая транзакционная база помогает двигаться быстрее и держать инварианты в одном месте.
В чистом монолите модуль заказов не должен ходить напрямую в таблицы биллинга, а модуль пользователей не должен знать детали складской логики. Граница проходит не по отдельному процессу, а по публичному API модуля. Такой подход сохраняет целостность данных и не заставляет команду платить налог распределённых систем раньше времени.
2. Команда быстрее выпускает изменения
Когда весь домен находится в одной кодовой базе, разработчику проще увидеть полный путь изменения: типы, бизнес-правила, миграции, UI-контракты и тесты. Рефакторинг проходит через один pull request, а не через цепочку релизов в пяти репозиториях. Это особенно важно для стартапов, внутренних платформ и продуктовых команд, где скорость проверки гипотез важнее формальной автономности сервисов.
Микросервисы дают независимость деплоя, но забирают простоту изменения сквозного сценария. Если сценарии постоянно меняются вместе, разделять их слишком рано экономически невыгодно.
3. Эксплуатация остаётся предсказуемой
Один сервис проще наблюдать: меньше сетевых переходов, меньше точек отказа, понятнее профилирование и дешевле инфраструктура. Логи, метрики, алерты и трассировки всё равно нужны, но их не приходится собирать из десятков независимых процессов с разными SLA и жизненными циклами.
Для большинства B2B-продуктов узким местом становится не количество сервисов, а качество запросов к базе, очереди фоновых задач, кэширование и горизонтальное масштабирование read-heavy частей. Эти задачи решаются внутри монолита: репликами PostgreSQL, Redis, worker-процессами, CDN и аккуратным разделением read/write путей.
4. Меньше DevOps-накладных расходов
Каждый новый сервис требует сборки, деплоя, секретов, мониторинга, лимитов, сетевых политик, документации и владельца. Если команда небольшая, эти расходы быстро съедают время, которое должно уходить на продукт. Чистый монолит не отменяет CI/CD, IaC и observability, но делает их компактнее: один релизный pipeline, один набор окружений, одна схема отката.
Это снижает стоимость владения. Бизнесу не нужно содержать платформенную команду только ради поддержки архитектурного выбора, который ещё не приносит измеримой отдачи.
5. Модульность можно контролировать тестами
Главный риск монолита - постепенное размывание границ. Его нужно закрывать инженерной дисциплиной: правилами импортов, архитектурными тестами, линтерами, code owners и контрактными тестами на уровне модулей. В TypeScript-проектах это можно усилить отдельными пакетами внутри workspace, публичными barrel-exports и запретом на доступ к внутренним директориям соседнего модуля.
src/
modules/
billing/
public.ts
domain/
infrastructure/
orders/
public.ts
domain/
infrastructure/
shared/
kernel/
ui/
Такая структура не мешает собрать приложение одним артефактом, но заставляет команду думать границами домена. Если модуль однажды придётся вынести в отдельный сервис, он уже будет подготовлен к разделению.
6. Производительность чаще упирается не в архитектурный стиль
Микросервисы не ускоряют систему автоматически. Они добавляют сериализацию, сетевые задержки, ретраи, idempotency и отдельную обработку частичных отказов. Если проблема в медленном SQL-запросе или тяжёлой синхронной операции, перенос кода в отдельный сервис только усложнит диагностику.
Чистый монолит позволяет сначала исчерпать более дешёвые меры: индексы, профилирование горячих участков, фоновые задачи, кэширование, денормализацию, батчинг и отдельные read-модели. Выделять сервис стоит тогда, когда есть конкретная причина: независимый масштаб нагрузки, разные требования к доступности, отдельная команда-владелец или необходимость изолировать риск.
7. Путь к микросервисам остаётся открытым
Чистый монолит не является тупиком. Он может быть первым этапом зрелой архитектуры: команда быстрее находит границы домена, проверяет бизнес-модель и понимает реальные профили нагрузки. После этого сервисы выделяются не по модному шаблону, а по данным из продакшена.
Хороший критерий прост: если модуль часто меняется независимо, имеет отдельный жизненный цикл, создаёт нагрузку другого типа и у него есть понятный владелец, его можно выносить. Если этих условий нет, лучше оставить его внутри монолита и инвестировать в качество границ.
Что мы рекомендуем
- Начинать новый продукт с модульного монолита и явной доменной структуры.
- Фиксировать публичные API модулей и запрещать обход внутренних слоёв.
- Держать CI быстрым: типизация, линтинг, unit-тесты, интеграционные проверки.
- Выносить фоновые и тяжёлые задачи в workers, не дробя домен без причины.
- Решение о сервисах принимать по метрикам нагрузки, релизного цикла и команды.
Архитектура должна снижать стоимость изменений. Для большинства команд чистый монолит делает именно это: оставляет систему понятной, сохраняет скорость разработки и не закрывает путь к дальнейшему разделению, когда оно действительно понадобится.


