Всем привет, меня зовут Сергей Прощаев. Последние несколько лет я проектирую высоконагруженные системы в финтехе и постоянно возвращаюсь к одному и тому же вопросу.
Мы в 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. Участие бесплатное, нужна регистрация.
Чтобы узнать, подойдет ли вам программа курса, пройдите вступительный тест.
Полный список бесплатных уроков от преподавателей курсов можно посмотреть в календаре мероприятий.
Комментарии (5)

kulikovalehandr
12.02.2026 14:22Согласен, без привязки к бизнес-границам микросервисы быстро превращаются в распределённый монолит.

Dhwtj
12.02.2026 14:22Да не нужны микросервисы сами по себе. Важно поддерживать простоту ядра, это цель. А если бездумно отрезать от монолита всё что попало то ничего не получится: остаток сохранит весь хаос. Да ещё и на грабли распределенной архитектуры по полной программе.
Берут монолит, в котором всё переплетено. Отрезают кусок, делают из него сервис. Оставшийся монолит не стал проще — он стал тем же хаосом минус один кусок, плюс сетевой вызов туда, где этот кусок был. Повторяют. Через год имеют распределённый монолит, где хаос сохранился, но добавились сетевые задержки, partial failures, eventual consistency там где она не нужна, и необходимость координировать деплой двадцати сервисов.
Остаток не стал проще, потому что никто не определил, что является ядром и как поддерживать его минимальность и простоту

Dhwtj
12.02.2026 14:22А, понял...
два принципиально разных подхода к управлению сложностью.Статья: уменьши blast radius, чтобы изменения в одном модуле не требовали изменений в других модулях.
Мой подход: обеспечь верифицируемость.Из минимизации blast radius не следует верифицируемость. А обратно, пожалуй, работает.
И для ERP like систем верифицируемость важнее.
Dhwtj
Очень поверхностно, хоть и верно.
Резать на микросервисы когда уже сформировалось г-но мамонта это не очень хороший паттерн. Отрезаем отрезаем и остаётся то же говно, которое уже не резрежешь.
Я щас склоняюсь к микроядерной (плагинной) архитектуре в CRM ERP like системах с богатыми агрегатами и сложными инвариантами. Изначально ядро минимальное. Дисциплина разработки не позволяет его усложнять, зато оно обозримое одним человеком. Всё второстепенное выносится наружу (плагины, DLL/so, микросервисы - как хотите): функционал без которого система ещё может использоваться (с плавной деградацией/устареванием данных и т.п.).
Таким образом, поддерживаются логическая верифицируемость и жёсткие контракты. Модуль / плагин / микросервис реализует контракт легко, через LLM.
То есть можете называть это микросервисами, но выбор между монолитным или распределенным деплоем, вопросы изоляции и восстановления после сбоя это вопрос к девопсам. А вопросы долгой жизни и развития приложения это не к девопсам а к software architect.
Dhwtj
Если чуть подробнее про микроядерную событийную архитектуру. А будете её писать монолитом или микросервисами дело ваше.
Суть архитектуры
Ядро как State Machine: Ядро содержит только описание состояний сущностей и логику валидности переходов. Оно не инициирует действия, а определяет критерии (контракты фактов), при которых переход в следующее состояние допустим.
Плагины как провайдеры фактов: Вся функциональная сложность вынесена в модули, которые поставляют ядру подтверждения (факты) о выполнении условий. Ядро не знает как получен факт, оно проверяет только его наличие и валидность относительно контракта.
Управляемая деградация: Отсутствие или задержка факта от плагина не вызывает системный сбой. Объект просто остается в текущем состоянии или переходит в «режим ожидания/ручной обработки». Система сохраняет работоспособность Core-функций при отказе любого количества второстепенных модулей.
Разделение консистентности: Ядро обеспечивает жесткую синхронную консистентность только для критических инвариантов внутри своих границ. Все остальные связи реализуются через асинхронную событийную модель (Eventual Consistency), где время доставки факта не влияет на стабильность ядра.
Почему это LLM-friendly
Изоляция контекста (Context Window): Для реализации плагина LLM не нужно понимать всё устройство ERP. Ей достаточно предоставить:
Схему входного события от ядра.
Спецификацию выходного факта (контракт).
Описание конкретной задачи (например, «сходи в API Сбербанка и подтверди оплату»).
Формальная верификация: Ответ плагина это структурированный факт (JSON/Protobuf). Его легко проверить автоматическими тестами на соответствие схеме ядра. Если LLM ошибается в логике, ядро просто отвергает факт как невалидный, не нарушая свою целостность.
Чистота контрактов: Поскольку плагины общаются с ядром через факты, а не через общую память или БД, исключаются побочные эффекты. LLM генерирует «чистую» функцию-адаптер, которую легко заменить или перегенерировать при изменении внешнего API, не затрагивая архитектуру системы.
Декларативность: Вместо написания сложных процедурных цепочек, вы просите LLM реализовать конкретное «обещание». Это совпадает с сильной стороной LLM - генерацией кода по четким спецификациям в рамках ограниченной ответственности..