В предыдущем материале — бизнес-бриф маркетплейса словами заказчика. Через две статьи — Tier C спецификация Order Service с агрегатом, Saga, Outbox и ABAC. Между ними — пласт, который чаще всего пропускают: переход «бриф → карта сервисов».

Без этого пласта AI быстро сгенерирует код по плохой спеке. Спека получается валидной по формату, но границы зафиксированы неверно. Сервис дрейфует к «общим таблицам», «двусторонней синхронизации», «без владельца у сущности» — и через полгода это переписывание.

В этой статье — семь шагов на маркетплейсе. Каждый — отсылка к глубокому материалу плюс конкретное решение для нашего кейса.


Шаг 1. Event Storming с бизнесом

Берём бриф маркетплейса и проходим его глазами участников. Цель — собрать доменные события в порядке времени: «покупатель добавил товар в корзину», «продавец подтвердил заказ», «модератор отклонил карточку», «выплата ушла продавцу».

Подробно про Event Storming как workshop — это отдельная техника, проводится с бизнесом и аналитиками за 2–4 часа.

Из брифа маркетплейса получаются ~50 событий, сгруппированных в 6 потоков:

  • Каталог: «продавец загрузил карточку», «модератор одобрил карточку», «покупатель добавил в избранное», «карточка снята с продажи».
  • Покупка: «корзина создана», «товар зарезервирован», «заказ оплачен», «заказ доставлен», «заказ подтверждён покупателем».
  • Деньги: «платёж принят», «деньги в эскроу», «выплата ушла продавцу», «возврат запрошен».
  • Уведомления: «отправить продавцу о новом заказе», «отправить покупателю об отгрузке», «напомнить о подтверждении».
  • Споры и поддержка: «спор открыт», «оператор взял в работу», «возврат денег».
  • Идентификация: «покупатель залогинился через телефон», «продавец прошёл MFA», «оператор маркетплейса начал смену».

Эти потоки — будущие границы сервисов. Не каждый поток автоматически становится сервисом, но каждый сервис вырастает из одного-двух потоков.


Шаг 2. Strategic DDD — Bounded Contexts

Из событий выделяем Bounded Contexts — куски системы со своим единым языком. В одном контексте «заказ» означает «финансовый документ с резервом товара», в другом — «строка таблицы для отчёта». Контексты должны быть отделены, чтобы языки не путались.

Подробно про стратегические паттерны DDD — Bounded Context, Context Map, Ubiquitous Language.

Для маркетплейса напрашиваются 6 контекстов:

КонтекстДоменное ядро
CatalogТовары, карточки, поиск, модерация
OrderКорзина, заказ, резерв товара, статусы заказа
PaymentПлатежи, эскроу, выплаты, комиссия маркетплейса
NotificationКаналы (email/sms/push), шаблоны, dispatch
CustomerПокупатели, продавцы, регистрация, авторизация
BackofficeМодерация, споры, аудит действий оператора

Это кандидаты на сервисы. Прямого решения «делать ли микросервисы» этот шаг не даёт — он говорит только «вот линии разлома, где-то по ним».


Шаг 3. Architectural Decision — монолит, модульный монолит или микросервисы

Тут включается отдельный пласт: как выбрать начальную архитектуру. Главные критерии: RPS, сроки, состав команды, организационная структура.

Для маркетплейса:

  • Целевой RPS в пике — десятки тысяч запросов в секунду на чтение каталога, тысячи на оформление заказов, единицы на платежи.
  • Сроки: первая версия за 6 месяцев, 4 фича-команды плюс платформа.
  • Команды: backend по 4–6 инженеров, фронт-команда отдельно, QA и DevOps распределены.
  • Скейлинг: чтение каталога должно скейлиться независимо от записи заказов.

Решение — микросервисы по контекстам, но без фанатизма:

  • Catalog — выделяется первым, потому что нагрузка чтения и команда поиска отдельные.
  • Order — отдельно из-за критичности данных (нельзя терять резервы и платежи).
  • Payment — отдельно по compliance (PCI-DSS scope изолируется).
  • Notification — отдельно по нагрузке и интеграциям с внешними каналами.
  • Customer и Backoffice — каждый свой сервис, но без серьёзного RPS.

Что не делаем: не дробим Catalog на Search, Pricing, Reviews по 4-5 микросервисам ради «правильности». Линии разлома идут по контекстам Event Storming, не по техническим слоям.


Шаг 4. Карта сервисов с границами

Получаем 6 сервисов. Для каждого — что он делает и что не делает (границы важнее центра):

diagram

Границы (что НЕ делает):

  • Catalog не считает деньги. Цена в карточке — справочная, реальная цена фиксируется в Order при оформлении.
  • Order не пишет в каталог. Резерв товара — это запись в Order, не в Catalog. Catalog только публикует «остаток N».
  • Payment не знает про заказы. Только про платежи и payout-планы. Связь с заказом — через external_ref.
  • Notification ничего не решает. Только отправляет по чужой команде. Логика «когда уведомить» — в источнике события (Order, Payment).
  • Customer не хранит профиль продавца как товары. Customer — это identity и auth, отдельно от каталога.
  • Backoffice не правит данные напрямую — действия идут через API соответствующего сервиса с записью в аудит.

Эти «не делает» — самое ценное в карте. Без них через год Catalog научится считать комиссию, а Order будет писать в каталог.


Шаг 5. Data Ownership — кто владеет какой сущностью

Каждая сущность имеет ровно одного владельца. Все остальные читают через интеграции, не лезут в чужую БД напрямую.

СущностьВладелецКто читает
Карточка товараCatalogOrder (snapshot при оформлении)
Остаток товараCatalogOrder (через резерв)
ЗаказOrderPayment, Notification, Backoffice
Платёж / эскроуPaymentOrder (статус), Backoffice
Выплата продавцуPaymentBackoffice
Профиль покупателяCustomerOrder, Notification
Профиль продавцаCustomerCatalog, Payment, Backoffice
Сессия / auth-токенCustomerВсе (через JWT/intro)
Шаблон уведомленияNotification
Аудит действийBackoffice

Принципы:

  • Snapshot при пересечении. Когда Order оформляется, цена, название, фото товара копируются в заказ. Если завтра продавец изменит карточку — заказ не меняется.
  • Чужой id через external_ref. Order ссылается на товар по productId, не реплицирует структуру карточки.
  • Для отчётов — отдельный read-model, не общая таблица. Backoffice читает через API или через event-stream, не делает JOIN на чужие БД.

Шаг 6. Integration Patterns — как сервисы общаются

Подробно про интеграционные паттерны DDD — ACL, OHS, Shared Kernel, Customer–Supplier и пр.

Для маркетплейса 4 типа взаимодействий:

1. Синхронные REST-вызовы — когда нужен ответ сейчас.

  • Web → Catalog (показать карточку)
  • Web → Order (создать заказ)
  • Order → Catalog (зарезервировать остаток)
  • Web → Customer (login)

2. Асинхронные доменные события через Kafka — когда сервису нужно отреагировать, но не блокировать.

  • OrderCreated → Notification (отправить продавцу), Payment (создать платёжное намерение)
  • PaymentCaptured → Order (перевести статус)
  • CardModerated → Catalog (опубликовать карточку), Notification (сообщить продавцу)

3. Saga для распределённой транзакции «оформление заказа» — резерв товара (Catalog) + платёж (Payment) + создание заказа (Order). Любой шаг падает — компенсация остальных. Подробно — в Tier C спеке Order Service.

4. Outbox + CDC для доставки событий — гарантия, что событие не потеряется при падении сервиса между «закоммитили в БД» и «опубликовали в Kafka».

Что не делаем:

  • Нет двусторонней синхронизации. Если Catalog изменился — Order узнаёт через snapshot или событие, не через «переспроси и обнови у себя».
  • Нет общей БД. Даже на старте — сервисы не разделяют схему PostgreSQL.
  • Нет sync-каскадов на 5 сервисов. Если Order должен спросить Catalog, потом Customer, потом Payment — это плохой дизайн. Один пользовательский запрос — не больше двух sync-хопов.

Шаг 7. Failure Domains — что при отказе соседа

На каждое sync-взаимодействие — план B.

ПадаетКто страдаетПлан B
CatalogWeb, OrderWeb показывает закешированные карточки. Order — отказ в оформлении (нельзя резервировать без актуального остатка).
OrderWebWeb показывает «оформление недоступно, попробуйте через минуту». Платёж не создаётся (нет точки входа).
PaymentOrderSaga ставит платёж в pending, retry до 24 часов. После — компенсация заказа.
CustomerВсеВсе читают JWT по publicKey локально, без обращения к Customer на каждый запрос. Только новые login заблокированы.
NotificationНикто не страдает. События остаются в Kafka, доставка отложена. Бизнес-нагрузка не блокируется.
BackofficeАудит копится в outbox, оператор работает позже.

Главный принцип: падение Notification и Backoffice не влияет на покупки. Падение Catalog или Order — влияет, но degrade graceful (старый кеш, отказ только в оформлении новых).

Эти решения принимаются до Tier C спеки, не после. Иначе придётся переписывать idempotency-ключи и retry-логику в коде.


Шаг 8. C4 Container — визуализация результата

Финал верхнего уровня — диаграмма C4 Container. Это то, что показывают новому разработчику, новому product-менеджеру, на due diligence.

Подробно про модель C4 — четыре уровня (Context, Container, Component, Code).

diagram

Container — это уровень, на котором обсуждается «вот тут у нас отдельный процесс, своя БД, свой деплой». Component (что внутри) — для каждого сервиса отдельно, в его Tier C спеке.


Дальше

Карта сервисов готова. Каждый сервис теперь получает свою Use Case спецификацию — на нужном Tier:

  • Catalog — Tier B (CQRS: чтение через Elastic, запись через PostgreSQL).
  • Order — Tier C (готовая спека — DDD-агрегат, Saga, Outbox, ABAC).
  • Payment — Tier C (PCI-zone, идемпотентность критична).
  • Notification — Tier A или B (готовая спека — channel-agnostic dispatch).
  • Customer — Tier B (OAuth2 + JWT, без сложной доменной логики).
  • Backoffice — Tier A (CRUD + аудит, без агрегатов).

Tier выбирается под доменную сложность сервиса, не под красоту единообразия. Catalog читается миллионами — нужен CQRS. Backoffice — 10 запросов в минуту, незачем городить агрегаты.


Что не пропустить

  • Без шага «карта сервисов» AI генерирует код по неверным границам. Это и есть то, что Алексей Толмачёв называет «6× меньше дефектов после формализации границ контекстов до кодогенерации».
  • ucp-spec-design — это про сервис, не про систему. Он не выберет тебе монолит vs микросервисы, не спросит о data ownership соседей. Эти решения должны быть на входе.
  • Спека Tier C без шага 4–7 — фасад на пустоте. Агрегат Order блестящий, но если границы между Order и Catalog нарисованы неверно — переписывание неизбежно.

Эта статья — «нулевой шаг» нашей методологии для целого сервиса. Один UseCase в существующем сервисе её не требует. Целая система — без неё начинать дороже, чем потратить день на её прохождение.