Микросервисы долго считались признаком зрелой архитектуры, и их стали внедрять там, где над продуктом работает небольшая команда. Расплата приходит быстро: инфраструктура, развёртывание и отладка отнимают больше времени, чем сама разработка. Мы регулярно приходим на такие проекты и видим одну и ту же картину — набор сервисов, половина которых ходит в общую базу, релиз всё равно выпускается целиком, а поднять проект у себя может один человек в команде. Разберём, что предлагает модульный монолит, чем он отличается от обычного монолита и в какой момент разделение на сервисы действительно окупается.
Откуда берётся лишняя сложность
Микросервисы решают в первую очередь организационную задачу: развязать несколько команд, чтобы каждая выпускала свою часть независимо от остальных. Если команд нет, а есть несколько разработчиков на весь продукт, развязывать нечего — зато все накладные расходы остаются в полном объёме.
Вот что появляется в проекте вместе с сервисами:
- Оркестрация и конфигурация: описания развёртывания, сети, секреты и доступы отдельно для каждого сервиса.
- Своя сборочная линия на каждый сервис и правила совместимости версий между ними.
- Распределённая трассировка и централизованный сбор логов — без них непонятно, в каком звене потерялся запрос.
- Согласование данных: то, что в одной базе было обычной транзакцией, превращается в сценарий с подтверждениями и откатами.
- Локальная разработка: чтобы проверить одну форму, приходится поднимать половину системы.
Каждый пункт по отдельности разумен. Вместе они дают постоянный налог на любую задачу, и платит его та же небольшая команда, которая должна была заниматься продуктом.
Что такое модульный монолит
Это одно приложение и одно развёртывание, но внутри — явно разделённые модули с описанными границами. Модуль не обращается к чужим таблицам напрямую, а вызывает соседа через интерфейс, который сосед сам объявил. Внутренности модуля можно переписывать сколько угодно, пока интерфейс остаётся прежним.
Отличие от привычного монолита не в количестве папок. В обычном проекте код разложен по техническим слоям: контроллеры, модели, шаблоны, вспомогательные службы. В модульном — по предметным областям: каталог, заказы, склад, расчёты, уведомления. Внутри области свои слои, но снаружи она выглядит как закрытый блок с несколькими понятными действиями.
Польза видна сразу. Задача «поменять правила скидок» перестаёт быть походом по десятку файлов в разных концах проекта: правки живут в одном месте, а список того, что может сломаться, ограничен явными вызовами модуля. Новый разработчик читает не весь проект, а одну область — и уже может закрывать по ней задачи.
Сравнение по затратам
| Что сравниваем | Модульный монолит | Микросервисы |
|---|---|---|
| Развёртывание | Одно приложение, один релизный цикл | Много единиц, оркестрация и порядок выкладки |
| Поиск причины ошибки | Обычная трассировка внутри одного процесса | Распределённая: нужны сбор логов и сквозной идентификатор запроса |
| Согласованность данных | Обычная транзакция в одной базе | Сценарии подтверждения и компенсирующих операций |
| Масштабирование | Копиями всего приложения | Точечно, по нагруженным частям |
| Требования к команде | Разработка | Разработка плюс эксплуатация и инфраструктура |
| Стоимость инфраструктуры | Низкая, предсказуемая | Заметно выше, растёт с числом сервисов |
| Скорость первой версии | Высокая: обвязки почти нет | Ниже: заметная часть времени уходит на обвязку |
| Цена ошибки в границах модулей | Перенос кода и переименование | Изменение протокола и согласованный выпуск нескольких сервисов |
Таблица не говорит, что микросервисы плохи. Она показывает, за что платят: за независимость выпуска и точечное масштабирование. Если этого проекту сейчас не нужно, счёт всё равно приходит.
Когда микросервисы оправданы
Мы считаем разделение обоснованным, когда выполняется хотя бы одно из условий:
- Несколько самостоятельных команд, которым мешает общий цикл выпуска и общая ветка.
- Части системы с принципиально разной нагрузкой: расчётный модуль загружен постоянно, а админка — изредка.
- Разные требования к доступности: сбой отчётов не должен ронять приём заказов.
- Обоснованный выбор другой технологии для отдельной задачи — например, обработки видео или поиска.
- Требования по изоляции данных, когда часть системы обязана жить в отдельном контуре.
Признак, по которому мы отличаем обоснованное разделение от моды, простой: можно назвать проблему, которая исчезнет после выделения сервиса, и способ убедиться, что она действительно исчезла. Ответы «так делают все» и «пригодится при росте» к этой проверке не относятся.
Как сделать монолит модульным
Порядок работ, которого мы придерживаемся на проектах:
- Описать предметные области словами бизнеса и договориться, что к какой относится. Спорные сущности — заказ, клиент, цена — разбираем отдельно: у них обычно разный смысл в разных областях.
- Разложить код по областям, ничего не переписывая. На этом шаге меняются только расположение файлов и пространства имён, поведение остаётся прежним.
- Развести данные: у каждой области свои таблицы, чужие читаются только через владельца. Общие справочники закрепляются за одним модулем.
- Объявить интерфейсы: перечень действий, которые модуль предоставляет наружу, и событий, о которых он сообщает.
- Закрыть обходные пути: прямые запросы в чужие таблицы, доступ к внутренним классам, общие вспомогательные функции с логикой сразу нескольких областей.
- Закрепить правила проверкой в сборке, а не устной договорённостью. Договорённость размывается за пару месяцев, проверка — нет.
Первые два шага дают заметный эффект уже сами по себе и не требуют останавливать разработку продукта. Остальное делается постепенно, область за областью.
Что делать с общими таблицами
Это самое трудное место. Обычно находится таблица, в которую пишут все: заказы, отчёты, интеграции. Разделять её сразу не нужно — сначала мы назначаем владельца и переводим остальных на чтение через него. Дальше видно, какие поля кому нужны на самом деле, и только после этого имеет смысл разносить хранение.
Что мы проверяем на ревью
Границы модулей держатся не на чертеже, а на ежедневной дисциплине. Список проверок короткий:
- Нет запросов к таблицам чужой области и обращений к её внутренним классам.
- Новая зависимость между модулями объяснена в описании задачи, а не появилась «чтобы быстрее».
- Изменение публичного интерфейса модуля отмечено отдельно: у него есть потребители.
- Общие вспомогательные функции не превращаются в свалку бизнес-правил из разных областей.
- Тесты модуля не поднимают половину системы: если поднимают, границы проведены неверно.
Как вынести модуль в отдельный сервис
Модульный монолит не тупик, а подготовленная площадка. Когда часть системы действительно потребует отдельного масштабирования или отдельной команды, порядок такой:
- Убедиться, что модуль общается с остальными только через объявленный интерфейс.
- Заменить прямые вызовы на сетевые, оставив приложение в одном развёртывании: так проверяются задержки и обработка отказов.
- Отделить данные модуля от общей базы.
- Вынести модуль в отдельное развёртывание и наблюдать за ним под реальной нагрузкой.
- Только после этого повторять процедуру для следующего модуля — по одному, а не всем составом.
Разница с распиливанием запутанного монолита существенная: зависимости уже известны, границы проведены заранее, и работа сводится к смене способа вызова, а не к раскопкам.
Про два частых возражения
«Монолит невозможно масштабировать» — приложение без состояния масштабируется копиями за балансировщиком, а ограничение обычно не в нём, а в базе данных, и у сервисов с общим хранилищем оно ровно то же. «Мы застрянем на одной технологии» — если для отдельной задачи действительно нужен другой инструмент, её выносят в один сервис, а не переводят на микросервисы весь продукт.
Если вы выбираете архитектуру для учётной или внутренней системы либо приводите в порядок разросшийся проект, мы поможем разложить его на модули и развести данные — это наша разработка баз данных и информационных систем. Начинаем с разбора текущих зависимостей, чтобы решение принималось по фактам, а не по моде.











