Всем привет, меня зовут Сергей Прощаев. Последние несколько лет я проектирую высоконагруженные системы в финтехе и постоянно возвращаюсь к одному и тому же вопросу.
Мы в IT умеем красиво рисовать кубики на архитектурных радарах. Сервис заказов, сервис доставки, сервис уведомлений. Кубики соединяются стрелочками — обычно RESTом или Kafka. Всё выглядит стройно.
Проблема в том, что эта стройность чаще всего иллюзорна
Встречал видел проекты, где «микросервисная архитектура» означала просто нарезанный монолит, обмотанный синхронными HTTP-вызовами. И в такой схеме если падал сервис пользователей — то за ним следом по цепочке валились ещё пять. Согласованность данных держалась на честном слове и ad-hoc скриптах. А на вопрос «почему граница сервисов проходит именно здесь?» разработчики пожимали плечами: «так исторически сложилось».
В этой статье я не буду в сотый раз перечислять паттерны из книги Криса Ричардсона, а покажу ход мыслей. Эти мысли будут на предмет того, как на старте не наступить на грабли и почему Domain-Driven Design — это не про «Entity» и Value Object», а в первую очередь про власть и границы, и в какой момент паттерн «Сага» становится вашим единственным спасением (ну или проклятием).
Давайте начнем.
Ловушка №1. «Просто разделим по слоям»
Представь: есть монолит — классическая трёхзвенка. Контроллеры, сервисы, репозитории, общая БД.
Команда решает: «Пора на микросервисы!» И первая мысль: давайте отделим слой работы с данными. Сделаем сервис «Database API», который будет отдавать данные по ID. И второй сервис — «Business Logic», который будет дёргать первый.
Поздравляю, вы только что изобрели распределённый монолит с задержкой в 30 миллисекунд вместо 2.
Уже через месяц или два в такой архитектуре непременно появятся:
циклические зависимости в рантайме, когда сервис А вызывал Б, Б вызывал В, В снова лез в А;
невозможность выкатить обновление без синхронного релиза трёх (или более) репозиториев;
падение всей системы при отказе одного инстанса БД.
Какой из этого можно сделать вывод?
Все очевидно — паттерн декомпозиции по техническим слоям (UI → Logic → Data) в микросервисной архитектуре — почти всегда приводит к плачевному результату, ведь он нарушает главный принцип: сервисы должны быть независимы по бизнесу, а не по функциям.
Давайте рассмотрим основные стратегии, с которых нужно было начать этот процесс.
Стратегия 1. Резать по бизнес-возможностям (и немного DDD)
В первой стратегии необходимо придерживаться золотого правила архитектуры, написанного кровью многих архитекторов, потерпевших неудачи: границы сервиса должны совпадать с границами поддомена!
Здесь нам нужен DDD. Но я не про тактический DDD (Entity, Aggregate Root). Я про стратегический DDD — про Bounded Context.
Как это выглядит в реальности?
Допустим, у нас есть интернет-магазин или маркетплейс, который предлагает посетителям разное барахло. Ассортимент товаров нас интересует меньше всего, а вот на архитектуре мы остановимся. Пусть это будет монолит по схеме «все в одном флаконе»: заказы, склад, оплата, клиенты.
Как можно накосячить сделать все не так как надо? Это просто — сделать сервис «Работа с базой данных заказов»
А как поступит Крис Ричардсон? Не знаком лично с Крисом, но уверен, что он сначала сделает сервис «Обработка заказа» (Order Fulfillment) и затем напилит создаст отдельный сервис «Управление каталогом» (Catalog).
Теперь спросите: «Chris, why is that?» Потому что у этих контекстов разная скорость изменения и разные эксперты. Каталог меняет маркетолог каждую неделю. Процесс заказа трогать без тестирования неделями страшно рискованно.
В практике обычно рекомендую не бросаться писать код, а сначала сделать простое упражнение: возьмите список всех функциональных требований и сгруппируйте их по существительным, которые меняются с разной частотой.
Если «цена товара» нужна и в каталоге, и в заказе — то это еще не повод объединять сервисы. Это повод задуматься, чья это ответственность. В каталоге цена — атрибут, а в заказе — снэпшот на момент покупки.
Граница сервиса — это граница автономии
Давайте возьмем в руки Mermaid и представим — как может выглядеть карта bounded context для нашего гипотетического ритейла интернет-магазина. То, что получилось изображено на рисунке 1:

Стратегия 2. Strangler Pattern — когда монолит ещё жив
Мы и все, что вокруг нас находится не в вакууме. И если вы читаете эту статью, то наверняка у вас есть legacy-система, которую нельзя остановить и которая приносит прибыль бизнесу. И здесь наилучшим образом Крис Ричардсон предложил бы вам рассмотреть применение паттерна «Душитель» (Strangler Fig)
Если лень читать по ссылке, то вкратце раскрою суть маньяка душителя — новую функциональность пишем сразу в микросервисах, а старую оставляем жить legacy и постепенно переводим на сервисы.
Такая схема оптимально ложиться на схему в которой используется фасад: старый монолит и новый сервис стояли рядом. Фасад смотрел: если запрос по новому API — шлём в новый сервис, если старый — в монолит.

Через год-полтора весь трафик уйдет в новую систему, и монолит можно по-тихому отключить и выпить шампанского.
Критическое условие. Фасад не должен содержать бизнес-логику! Фасад содержит только маршрутизацию, иначе он сам станет монолитом.
В архитектурных практиках есть несколько вариантов реализации паттерна «Душитель» в некоторых запрос от Клиента с Фасада может уходить сначала в Legacy Монолит и далее уже Монолит его адресует в новый выделенный сервис.
Стратегия 3. Database per Service
Если у вас один сервис пишет в ту же таблицу, что и другой — у вас нет микросервисов. У вас есть слабосвязанный код, прибитый гвоздями к общей схеме данных.
Паттерн Database per Service — обязателен но он порождает главную проблему микросервисов: как делать JOIN через границы сервисов?
Вот здесь мы подходим к самому интересному.
Как работать с данными: CQRS и Saga
1. CQRS — не ради моды, а ради изоляции
Если чтение данных сложное, требует агрегации из 5 сервисов, а запись — простая вставка в одну таблицу — не лучшим вариантом будет использования REST-агрегатора.
Лучше разделить Command и Query и держать отдельную read-модель, которая обновляется асинхронно по событиям.
В одном проекте админка требовала «показать все заказы клиента с его бонусами и историей доставок». В монолите — один SELECT с 3 JOIN. В микросервисах — пришлось дёргать 4 сервиса синхронно. Время ответа — 800 мс.
Сделали простую материализованную вьюху в отдельной read-БД, которая обновлялась по CDC из исходных сервисов. Время упало до 40 мс. Да, это Eventual Consistency. Но для аналитики админа это ок!
2. Saga — когда ACID уже не спасает
Распределённые транзакции — наша плата за независимость.
Здесь есть два подхода.
Хореография. Сервисы слушают события и сами решают, что делать. Минус — логика размазана по коду, через полгода никто не помнит, кто на какие события подписан.
Оркестрация. Один класс (SagaOrchestrator) говорит: «шаг 1 — сервис А, шаг 2 — сервис Б, если ошибка — запусти компенсацию».
Я предпочитаю оркестрацию. Да, это «единый контролёр», но зато поток транзакции читается как сценарий теста.
Три вопроса, которые я задаю себе перед тем, как создать новый сервис
Иногда бывает и больше трех, но три это показатель мастерства. Начинаем последовательно:
Может ли этот сервис жить без другого?
Если для ответа на запрос мне нужно синхронно сходить в соседний сервис — у меня плохо с автономией. Возможно, граница проведена не там.Что произойдёт, если база данных этого сервиса упадёт?
Если упадёт всё приложение целиком — это плохой сервис. Хороший сервис кэширует, отдаёт fallback или хотя бы вежливо говорит «недоступно».Я смогу переписать этот сервис на другом языке через год?
Если да — автономия есть. Если нет, потому что он слишком завязан на shared-коде — это распределённый монолит.
Заключение: главный паттерн — это осознанность
Микросервисы — это не про технологию. Это про границы.
Паттерны декомпозиции (по бизнес-возможностям, по поддоменам, Strangler) нужны не для красоты. Они нужны, чтобы сделать границы осмысленными. Когда каждый сервис — это чей-то «кусочек власти» и ответственности, код начинает структурировать сам себя.
Если вам кажется, что я рассказываю очевидные вещи — это хорошо. Значит, вы уже прошли через катастрофы распределённых транзакций и настройку circuit breaker.

А если вы только начинаете и хотите не просто «знать паттерны», а понимать, как их выбирать под конкретную нагрузку и бюджет, — приглашаю на открытый урок курса «Highload Architect» в OTUS 18 февраля в 20:00. Участие бесплатное, нужна регистрация.
Чтобы узнать, подойдет ли вам программа курса, пройдите вступительный тест.
Полный список бесплатных уроков от преподавателей курсов можно посмотреть в календаре мероприятий.
