В предыдущем материале — бизнес-бриф маркетплейса словами заказчика. Через две статьи — 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 сервисов. Для каждого — что он делает и что не делает (границы важнее центра):
Границы (что НЕ делает):
- 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 — кто владеет какой сущностью
Каждая сущность имеет ровно одного владельца. Все остальные читают через интеграции, не лезут в чужую БД напрямую.
| Сущность | Владелец | Кто читает |
|---|---|---|
| Карточка товара | Catalog | Order (snapshot при оформлении) |
| Остаток товара | Catalog | Order (через резерв) |
| Заказ | Order | Payment, Notification, Backoffice |
| Платёж / эскроу | Payment | Order (статус), Backoffice |
| Выплата продавцу | Payment | Backoffice |
| Профиль покупателя | Customer | Order, Notification |
| Профиль продавца | Customer | Catalog, 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 |
|---|---|---|
| Catalog | Web, Order | Web показывает закешированные карточки. Order — отказ в оформлении (нельзя резервировать без актуального остатка). |
| Order | Web | Web показывает «оформление недоступно, попробуйте через минуту». Платёж не создаётся (нет точки входа). |
| Payment | Order | Saga ставит платёж в 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).
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 в существующем сервисе её не требует. Целая система — без неё начинать дороже, чем потратить день на её прохождение.