.NET Tutorial
Senior6 мин чтенияВопрос 10 из 17

Microservices vs Monolith — когда что выбирать?

Три варианта

Монолит

Одно приложение:
всё со всем связано

Одна БД,
общие таблицы

Модульный монолит

Один процесс

Модуль Orders

Модуль Payments

Модуль Catalog

Одна БД,
схемы orders / payments / catalog

Микросервисы

события

API Gateway

Orders

Payments

Catalog

orders_db

payments_db

catalog_db

Брокер

Монолит

Одно приложение:
всё со всем связано

Одна БД,
общие таблицы

Модульный монолит

Один процесс

Модуль Orders

Модуль Payments

Модуль Catalog

Одна БД,
схемы orders / payments / catalog

Микросервисы

события

API Gateway

Orders

Payments

Catalog

orders_db

payments_db

catalog_db

Брокер

Монолит, модульный монолит, микросервисы

Сравнение

Критерий Монолит Модульный монолит Микросервисы
Развёртывание Всё целиком Всё целиком Каждый сервис отдельно
Скорость старта проекта Максимальная Высокая Низкая (инфраструктура)
Транзакции ACID ACID (внутри), события между модулями Saga, eventual consistency
Вызовы между частями Вызов метода (нс) Вызов метода через контракт Сеть (мс), может упасть
Масштабирование Целиком Целиком Точечно по сервисам
Изоляция отказов Нет — утечка памяти роняет всё Нет Да (при правильной устойчивости)
Независимость команд Низкая Средняя Высокая
Технологическая свобода Одна платформа Одна платформа Любая на сервис
Отладка и тестирование Просто Просто Сложно: трассировка, контракты, e2e
Требования к DevOps Низкие Низкие Высокие: CI/CD на каждый, K8s, мониторинг
Риск «Большой ком грязи» Нарушение границ модулей Распределённый монолит

Цена распределённости

Микросервисы

Сеть: задержки, таймауты,
частичные отказы

Согласованность данных:
Saga, Outbox, идемпотентность

Наблюдаемость:
распределённая трассировка,
централизованные логи, метрики

Деплой: CI/CD на сервис,
контейнеры, оркестрация

Версионирование контрактов,
обратная совместимость

Тестирование: контрактные тесты,
тестовые окружения

Безопасность: сервис-сервис
аутентификация, секреты

Микросервисы

Сеть: задержки, таймауты,
частичные отказы

Согласованность данных:
Saga, Outbox, идемпотентность

Наблюдаемость:
распределённая трассировка,
централизованные логи, метрики

Деплой: CI/CD на сервис,
контейнеры, оркестрация

Версионирование контрактов,
обратная совместимость

Тестирование: контрактные тесты,
тестовые окружения

Безопасность: сервис-сервис
аутентификация, секреты

Что добавляется при переходе на микросервисы

Восемь заблуждений о распределённых вычислениях (Питер Дойч): сеть надёжна; задержка нулевая; пропускная способность бесконечна; сеть безопасна; топология не меняется; администратор один; транспорт бесплатен; сеть однородна.

Распределённый монолит — худший вариант

Признаки:

  • Сервисы нельзя выкатить независимо — релиз требует синхронного обновления нескольких.
  • Общая БД, в которую пишут несколько сервисов.
  • Цепочки синхронных HTTP-вызовов: Orders → Customers → Loyalty → Pricing; падение любого роняет всё.
  • Общая библиотека доменных моделей, изменение которой требует пересборки всех.

Получаем все минусы и монолита (связанность), и микросервисов (сеть, сложность).

Как делить: по бизнес-возможностям

Неправильно: по техническим слоям

UI-сервис

Сервис бизнес-логики

Сервис доступа к данным

Правильно: по бизнес-возможностям

Заказы

Платежи

Каталог

Доставка

Неправильно: по техническим слоям

UI-сервис

Сервис бизнес-логики

Сервис доступа к данным

Правильно: по бизнес-возможностям

Заказы

Платежи

Каталог

Доставка

Декомпозиция по Bounded Contexts, а не по техническим слоям
  • Границы сервиса = Bounded Context (см. DDD): высокая связность внутри, слабая связанность снаружи.
  • Каждый сервис владеет своими данными; другие получают их через API или события, никогда не напрямую из чужой БД.
  • Закон Конвея: архитектура системы повторяет структуру коммуникаций организации. Микросервисы работают, когда у каждого сервиса есть команда-владелец.

Модульный монолит в .NET

Текст
src/
├── Shop.Host/                          # один ASP.NET Core процесс, composition root
├── Modules/
│   ├── Orders/
│   │   ├── Shop.Orders.Contracts/      # публичный API модуля: интерфейсы, DTO, интеграционные события
│   │   └── Shop.Orders/                # internal-реализация, своя схема БД "orders", свой DbContext
│   ├── Payments/
│   │   ├── Shop.Payments.Contracts/
│   │   └── Shop.Payments/
│   └── Catalog/ ...
└── Shared/Shop.BuildingBlocks/         # инфраструктура: outbox, шина событий в памяти

Правила:

  • Модули ссылаются только на Contracts других модулей (проверяется архитектурными тестами).
  • У каждого модуля своя схема БД и свой DbContext; JOIN-ы между схемами модулей запрещены.
  • Взаимодействие — через публичные интерфейсы или внутрипроцессные события (с outbox — готово к выносу в брокер).
  • Выделение модуля в сервис: заменить in-process вызовы на HTTP/gRPC и шину на брокер.

Когда переходить на микросервисы

да

нет

да

нет

да

нет

Несколько команд мешают
друг другу в одной кодовой базе,
релизы блокируются?

Выделять сервисы
по Bounded Context

Части системы требуют сильно
разного масштабирования
или технологий?

Нужна изоляция отказов
критичной части?

Модульный монолит

да

нет

да

нет

да

нет

Несколько команд мешают
друг другу в одной кодовой базе,
релизы блокируются?

Выделять сервисы
по Bounded Context

Части системы требуют сильно
разного масштабирования
или технологий?

Нужна изоляция отказов
критичной части?

Модульный монолит

Решение

Предпосылки успеха: зрелый CI/CD, контейнеры и оркестрация, наблюдаемость (трассировка, логи, метрики), культура владения сервисами, понятные границы домена.

Вопросы на засыпку

Почему нельзя делить базу данных между сервисами?

Общая БД — скрытый контракт: изменение схемы одним сервисом ломает другой, нельзя деплоить независимо, неясно, кто владеет данными и инвариантами. Каждый сервис — владелец своих данных; другие получают их через API или подписываясь на события (и хранят локальную копию нужного).

Как сервисы получают данные друг друга без синхронных вызовов?

Через события: сервис Orders подписывается на ProductPriceChanged и хранит у себя нужную проекцию каталога. Запросы становятся локальными и быстрыми, а отказ Catalog не мешает оформлению заказов. Цена — eventual consistency и дублирование данных.

Что такое «микросервис правильного размера»?

Не строки кода, а границы: сервис реализует одну бизнес-возможность, может разрабатываться одной командой, деплоиться независимо и не требует синхронных изменений других сервисов. «Слишком маленькие» сервисы (наносервисы) порождают болтливые сетевые взаимодействия и распределённый монолит.

Правда ли, что микросервисы быстрее?

Нет, сами по себе они медленнее: сетевые вызовы, сериализация. Выигрыш — в независимом масштабировании горячих частей и изоляции. Монолит на хорошем сервере обслуживает огромные нагрузки (Stack Overflow — известный пример).

Итог

Монолит — лучший старт: просто и быстро. Модульный монолит добавляет строгие границы и сохраняет простоту эксплуатации. Микросервисы решают организационные проблемы масштаба и дают независимость ценой распределённой системы. Переходите к ним осознанно — по бизнес-границам, с владением данными и зрелой инфраструктурой, иначе получите распределённый монолит.