Когда микросервисы — плохая идея
Как распознать преждевременное дробление, почему «распределённый монолит» хуже обоих вариантов и какое условие должно выполниться в организации, прежде чем резать систему.
Распределённый монолит — система, разрезанная на сервисы физически, но не логически: выкатить, протестировать и починить сервис по отдельности невозможно. Худший из двух миров — сложность микросервисов без единой их выгоды.
Микросервисы редко проваливаются громко. Обычно всё выглядит прилично: репозитории на месте, Kubernetes крутится, дашборды красивые. Просто фичи почему-то едут в три раза медленнее, чем год назад, а каждый релиз превращается в созвон восьми человек. Это и есть провал — тихий, растянутый на месяцы. Чтобы его не допустить, надо уметь читать симптомы заранее.
Признаки преждевременного дробления
Дробить рано, если про вашу систему верно хотя бы три пункта из этого списка:
- Предметная область ещё меняется. Раз в месяц вы переосмысливаете, что такое «заказ» или «клиент». Границы сервисов при таких сдвигах приходится двигать вместе с данными.
- Команда меньше десяти человек. Микросервисы — лекарство от координации людей. Если координироваться не с кем, вы пьёте таблетку от несуществующей болезни.
- Нет автоматического деплоя. Если один сервис выкатывается руками — двадцать не выкатятся никак.
- Нет централизованных логов и трассировки. Первый же межсервисный баг будет расследоваться днями.
- Нагрузка не требует раздельного масштабирования. Тысяча запросов в минуту прекрасно живёт на одном приложении и одной базе.
- Аргумент за дробление звучит как «так делают в больших компаниях». В больших компаниях делают так, потому что у них двести команд.
Распределённый монолит: как он выглядит
Это не карикатура, а самый частый результат преждевременного дробления. Сервисы есть, автономности нет. Проверьте себя по таблице — правая колонка проверяется буквально за один вечер.
| Симптом | Что это значит на самом деле | Как проверить |
| «Выкатываем всё вместе по четвергам» | у сервисов общий цикл релиза — то есть общий деплой, как у монолита | сколько релизов сервиса A прошло без релиза B за последний месяц? |
| Общая база данных на несколько сервисов | сервисы связаны через схему; миграция ломает соседей | сколько разных сервисов ходят в таблицу orders? |
| Общая библиотека с DTO и моделями | изменение одного поля требует пересборки и релиза всех | как часто меняется пакет common и сколько сервисов его тянут? |
| Каждый пользовательский запрос идёт по цепочке из 5+ сервисов | отказ любого звена = отказ всей функции | откройте трассу типичного запроса и посчитайте сетевые хопы |
| Задача в спринте почти всегда трогает 2–3 репозитория | граница проведена не по шву предметной области | посчитайте долю задач с кросс-сервисными изменениями |
| Локально ничего не работает без всей системы | сервисы не автономны даже в разработке | может ли новичок запустить один сервис и его тесты? |
Типичный распределённый монолит на схеме выглядит так — обратите внимание, что база одна:
HTTP HTTP HTTP
api-gw ------> orders ------> billing ------> notify
| | |
+--------------+--------------+
|
[ одна общая БД ]
Отказ notify роняет оформление заказа.
Миграция в БД требует одновременного релиза трёх сервисов.
Итог: сложность микросервисов, независимость — нулевая.
Организационное условие: закон Конвея
Организации проектируют системы, копирующие структуру их коммуникаций. — Мелвин Конвей, 1967
Формулировка выглядит как афоризм, но у неё очень практичное следствие: архитектура, которая не совпадает со структурой команд, не выживает. Если пять разработчиков владеют двадцатью сервисами, то на бумаге у каждого по четыре, а на деле не владеет никто: любой сервис чинит тот, кто первым увидел алерт, никто не знает контракт целиком, и через полгода все двадцать сервисов деплоятся вместе — потому что так проще договориться. Система схлопнулась обратно в монолит, только теперь он ходит сам к себе по HTTP.
Отсюда правило: у сервиса должна быть команда-владелец. Команда может владеть несколькими сервисами — это нормально. Но не бывает сервиса, которым владеет «половина команды» или «все понемногу». Опытные компании используют этот закон намеренно — «обратный манёвр Конвея»: сначала перестраивают команды под желаемую архитектуру, и только потом режут код.
| Сколько у вас команд | Что разумно | Почему |
| Одна (2–8 человек) | модульный монолит | координировать некого; налог на распределённость не окупится |
| Две-три | монолит плюс один-два вынесенных сервиса | выносим только то, у чего есть особая причина: другая нагрузка, другой стек, другой цикл релиза |
| Четыре и больше, домены разные | микросервисы оправданы | боль от координации уже больше, чем боль от сети |
Входной билет: что надо иметь до первого сервиса
Микросервисы — это не про код, а про эксплуатацию. Прежде чем резать, убедитесь, что у вас есть:
- автоматический CI/CD, который выкатывает сервис в прод без ручных шагов и умеет откатывать;
- инфраструктура как код: новый сервис поднимается по шаблону за часы, а не за недели;
- централизованные логи, метрики и распределённая трассировка со сквозным correlation-id;
- алерты и понятный дежурный: кого будят, если упал сервис;
- контрактные тесты между сервисами — иначе несовместимость вы узнаете от пользователей;
- договорённость о версионировании API и обратной совместимости.
Если хотя бы половины из этого нет — дробить рано. Не потому, что «вы не доросли», а потому, что каждый недостающий пункт превращается в ночной инцидент.
Как это работает: почему общая база убивает независимость
Самая соблазнительная ошибка — разрезать код, но оставить одну базу: «зачем плодить сущности, там же всё связано». Посмотрите, что происходит дальше.
Было: orders.status = 'paid' | 'shipped'
Стало: orders.state = 'PAID' | 'SHIPPED' (переименование + новый формат)
Кто читает таблицу orders напрямую:
- orders-service (свой код, знает про миграцию)
- billing-service (чужая команда, про миграцию не знает)
- analytics-jobs (ночной скрипт, вспомнят через неделю)
Значит:
1) миграцию нельзя выкатить в одиночку — нужен общий релиз;
2) откатить сервис отдельно тоже нельзя — схема уже другая;
3) чтобы не сломать соседей, приходится согласовывать каждый ALTER.
Вывод: общая схема = общий цикл релиза = монолит по сети.
Правило, из которого не бывает исключений: у каждого сервиса своя база (или хотя бы своя схема), и никто, кроме владельца, не ходит в неё напрямую. Данные соседа получают только через его API или через события. Да, это значит дублирование данных и отказ от удобного JOIN. Именно за эту цену покупается право выкатываться в одиночку — а это и есть единственная настоящая выгода микросервисов.
Частые ошибки
- Резать «на будущее». Система на 5 тысяч запросов в день не нуждается в двенадцати сервисах — она нуждается в понятных модулях.
- Оставить одну базу. Самый быстрый способ получить распределённый монолит без единой выгоды.
- Общая shared-библиотека с моделями. Кажется удобством, работает как жёсткая связь: релиз библиотеки становится релизом всей системы.
- Синхронные цепочки на каждый чих. Если оформление заказа синхронно ждёт сервис уведомлений — упавшая почта уронит продажи.
- Дробить, не меняя команды. Пока за код отвечают «все», архитектура по закону Конвея сползёт обратно в общий деплой.
- Считать возврат к монолиту поражением. Слить обратно два сервиса, которые всегда меняются вместе, — не позор, а нормальный инженерный ход.
Итоги
- Преждевременное дробление даёт распределённый монолит: сложность есть, независимости нет.
- Главные маркеры: общая база, общий релиз, общая библиотека моделей, кросс-сервисные задачи в каждом спринте.
- Закон Конвея: у сервиса должна быть команда-владелец, иначе архитектура схлопнется обратно.
- До первого сервиса нужны CI/CD, IaC, логи, метрики, трассировка, алерты и контрактные тесты.
- Единственная реальная выгода микросервисов — независимый деплой. Нет её — нет и микросервисов, как бы ни выглядела схема.