Когда монолит не нужно разбивать
Микросервисы звучат современно, но декомпозиция монолита без достаточных оснований создаёт больше проблем, чем решает. Разбираю, как думать об этом решении.
Слово "монолит" в разговорах об архитектуре всё чаще произносится с извиняющейся интонацией. Как будто это само по себе проблема, которую нужно исправить. При этом ни один из серьёзных разработчиков микросервисной архитектуры не говорил, что монолит всегда плохо. Говорили обратное: микросервисы имеют смысл только при определённых условиях, и попытка их применить до созревания этих условий стоит дорого.
Я вижу в последнее время несколько проектов, где команды ввязались в декомпозицию монолита по причинам, которые не выдерживают проверки. Хочу описать, как я думаю об этом вопросе.
Что на самом деле решает декомпозиция
Микросервисная архитектура решает конкретные проблемы масштабирования: независимое развёртывание частей системы, масштабирование только нагруженных компонентов, независимые технологические стеки для разных команд.
Всё это имеет смысл, когда есть несколько независимых команд, которым мешает совместное владение одним репозиторием. Или когда части системы имеют принципиально разные требования к нагрузке. Или когда скорость независимого деплоя критически важна.
Если ни одно из этих условий не выполняется - декомпозиция не решает проблему, потому что проблемы нет. Она создаёт новые: сетевые вызовы вместо вызовов функций, распределённые транзакции, сложность отладки, оркестрация сервисов. Это реальный операционный overhead, который платится независимо от того, насколько "современно" звучит архитектура.
Признаки того, что разбивать не нужно
Первый признак: команда состоит из 3-7 человек и работает над одним продуктом. Микросервисы решают проблему координации больших команд. При маленькой команде это решение без задачи.
Второй признак: продукт ещё не устоялся. Если требования меняются каждые несколько недель, жёсткие границы между сервисами станут препятствием, а не помощью. Рефакторинг интерфейсов между сервисами обходится значительно дороже, чем рефакторинг внутри монолита.
Третий признак: нагрузка на систему равномерная и предсказуемая. Если нет части системы, которая нагружена принципиально иначе, чем остальные, независимое масштабирование не нужно.
Четвёртый признак: проблема в коде, а не в архитектуре. Плохо организованный монолит можно сделать чище без разбивки на сервисы. Декомпозиция хаотичного монолита на сервисы даёт распределённый хаос - каждую из проблем теперь сложнее найти и исправить.
Что делать вместо этого
Если есть жалобы на то, что монолит "неудобно поддерживать" - стоит разобраться, в чём конкретно неудобство. Часто ответ находится внутри монолита: нет чётких модульных границ, логика перемешана, нет тестов, нет документации.
Модульный монолит с чёткими внутренними границами между компонентами решает большинство проблем поддерживаемости без накладных расходов распределённой системы. Если в будущем потребуется выделить модуль в сервис - это можно сделать. Но делать это заранее, до того как возникнет реальная потребность, - значит платить цену сейчас за гипотетическую пользу в будущем.
Вопросы перед решением
Если в вашей команде обсуждается переход на микросервисы, несколько вопросов, которые стоит задать до начала работы:
- Какую конкретную проблему мы решаем - и есть ли у неё другое, более простое решение?
- Сколько независимых команд будут развёртывать разные части системы?
- Есть ли компоненты с принципиально разными требованиями к нагрузке?
- Кто будет отвечать за оркестрацию, мониторинг и отладку распределённой системы?
- Что произойдёт, если выяснится, что мы провели границы между сервисами неправильно?
Последний вопрос особенно важен. Неправильные границы в микросервисной архитектуре - одна из самых дорогих ошибок, которую трудно откатить.