Граница модуля или граница сервиса?
- 01МодульОдин релиз · локальный вызов
- 02СервисСвой релиз · сетевой контракт
Оба подхода рабочие. Монолит не обязан быть неуправляемым, а микросервисы не становятся отказоустойчивыми только из-за того, что процессов стало больше.
Для нового продукта мой базовый вариант — модульный монолит с явными границами внутри. Микросервис появляется тогда, когда независимая граница решает уже наблюдаемую проблему и команда готова её эксплуатировать.
Разница, за которую приходится платить
| Критерий | Модульный монолит | Микросервисы |
|---|---|---|
| Единица релиза | Приложение целиком | Отдельный сервис |
| Вызовы | Локальные | Сетевые: таймауты, повторы, частичные отказы |
| Данные | Проще общая транзакция | Нужны владельцы данных и явная модель согласованности |
| Масштабирование | Обычно приложение масштабируется целиком | Можно отдельно масштабировать горячие компоненты |
| Диагностика | Меньше межсервисных границ; реплики и фоновые процессы всё равно требуют наблюдения | Логи, метрики и трассировка по нескольким компонентам |
| Организация разработки | Границы поддерживаются в коде | Границы дополнительно закреплены владением и релизом |
Сеть может изолировать отказ, но может и распространить его каскадом. Независимый деплой работает только при совместимых контрактах. Отдельная база уменьшает связанность команд, но усложняет операции, которым нужна согласованность нескольких доменов.
Семь вопросов перед решением
1. Граница домена уже понятна?
Если команда ещё регулярно переносит ответственность между модулями, физическое разделение закрепит случайную модель. Сначала полезнее выстроить границы внутри одного приложения.
2. Кому принадлежат код и данные?
Сервис без одного владельца быстро становится общей интеграционной зоной. Нужна команда, которая отвечает за контракт, данные, SLO, выпуск и поддержку.
3. Что именно тормозит релиз?
Если независимая функция неделями ждёт общего окна, отдельный выпуск может помочь. Если задержка вызвана ручным тестированием или согласованиями, дробление кода не устранит причину.
4. Нагрузка действительно различается?
Отдельное масштабирование полезно, когда небольшой компонент потребляет непропорционально много ресурсов. Равномерно растущий продукт можно масштабировать репликами монолита.
5. Нужна отдельная граница отказа или доступа?
Это сильный аргумент, если сервис способен деградировать независимо и его зависимости имеют таймауты, лимиты и сценарии восстановления. Без этого новый сетевой вызов лишь расширит поверхность отказа.
6. Как будет обеспечиваться согласованность данных?
Локальная транзакция одной базы не охватывает изменения в независимых хранилищах автоматически. Часто нужны идемпотентные команды, события, компенсации и явно допустимые промежуточные состояния. Распределённая транзакция тоже возможна, если её поддерживают участники, но добавляет координацию и ограничения при отказах. Выбор согласованности — бизнес-решение, а не деталь транспорта.
7. Команда готова к эксплуатации?
Для нескольких сервисов нужны автоматизированный выпуск, наблюдаемость, управление секретами, контрактные тесты, дежурство, runbooks и понятный откат. Оркестратор не создаёт эти практики автоматически.
Что не является достаточным аргументом
- «Когда-нибудь вырастем».
- «Так делают крупные компании».
- «Хотим разные языки для каждого модуля».
- «У нас уже есть Kubernetes».
- «Микросервисы современнее».
Все эти фразы могут сопровождать решение, но не объясняют его экономику.
Практичный путь
- Разделить домены и зависимости внутри монолита.
- Измерить время изменения, выпуска и восстановления.
- Выбрать одну границу с понятным владельцем и эффектом.
- Отделить контракт и данные без большого переписывания.
- Сравнить показатели до и после.
Если выделение сервиса не уменьшило связанность, время выпуска или радиус отказа, архитектура стала сложнее без полезного результата.
Выбирать стоит не между «старым» и «современным», а между двумя моделями затрат. Хорошее решение — то, которое команда сможет менять и поддерживать без постоянного героизма.
Учебный пример: сервис уведомлений
Представим продукт, в котором заявки, расчёты и отправка уведомлений находятся в одном приложении. Почтовый провайдер периодически отвечает медленно. Команда предлагает выделить уведомления в микросервис.
Первый вопрос — что именно хотим изолировать. Если пользовательский запрос ждёт отправки письма, очередь задач может снять зависимость и внутри монолита. Если же у уведомлений отдельная команда, собственные релизы, несколько каналов и независимый профиль нагрузки, сервисная граница получает дополнительное обоснование.
Но после выделения остаются вопросы: как событие попадёт в очередь вместе с изменением заявки, как распознать дубль, кто увидит неотправленное письмо и что произойдёт при смене шаблона. Самостоятельный деплой не решает эти задачи. Он лишь позволяет одной команде выпускать их реализацию независимо.
Я бы сравнил два небольших проекта решения: модуль с фоновой обработкой и отдельный сервис. Для каждого — владелец, путь сбоя, изменение данных, выпуск, мониторинг и стоимость сопровождения. Если разница в пользу сервиса пока не видна, сохранение модуля оставляет более дешёвый вариант для проверки продукта.
Запись решения, к которой можно вернуться
Полезный ADR содержит контекст, выбранный вариант, отвергнутые альтернативы и признаки пересмотра. Например: возвращаемся к разделению, когда независимые релизы уведомлений регулярно блокируются общим циклом и у направления появляется собственная команда. Это лучше, чем бессрочное обещание «потом перейдём на микросервисы».
Базовые признаки сервисного подхода разобраны в статье James Lewis и Martin Fowler о микросервисах. Пример выше — моя иллюстрация выбора, а не утверждение, что один стиль подходит любой организации.
Из моего Telegram-канала @it_underside.
Ваш ДПУПП
Похожие статьи
EventStorming: польза, ограничения и практика
Практический разбор EventStorming: какие задачи решает метод, где возникают ограничения и как подготовить сессию, чтобы команда получила полезный результат.
Форматы обмена данными: JSON, XML, Protobuf, Avro и MessagePack
Как выбирать формат не по моде, а по контракту: эволюция схем, обратная и прямая совместимость, инструменты и измеренная стоимость обработки.
UML на практике: где диаграмма помогает, а где становится лишней
Как выбрать UML-диаграмму под конкретный вопрос, определить срок её жизни и не превратить моделирование в отдельный проект.