DDD Strategic Design — bounded contexts (Sales, Billing, Shipping), Generic subdomains (Auth0 conformist, SendGrid), Legacy CRM with ACL. Context map shows Customer/Supplier, Published Language, Conformist, ACL relationships. Scenarios: cross-context Customer flow (Lead -> Payer -> Recipient), ACL protecting against legacy XML, Conformist trade-off, anti-pattern microservice-per-table.
Большинство архитектурных провалов — это не «плохая технология», это неправильно проведённые границы. Команда положила всё в один монолит и через два года не может выпустить релиз без созвона на 12 человек. Или нарезала систему на 50 микросервисов «по таблицам» — и получила distributed monolith, где «независимые» сервисы деплоятся только все вместе. Или сделала универсальный User на 50 полей, потому что каждая команда дописала туда своё.
Strategic DDD (Eric Evans, Blue Book, 2003) — это набор инструментов для нахождения правильных границ: bounded contexts, ubiquitous language, context maps, разделение на core/supporting/generic. Это не про код и не про паттерны вроде агрегатов (это tactical DDD). Это про карту территории: как разделить большую систему, кто чем владеет, как контексты интегрируются.
Главный сдвиг сознания: «Customer» не одно и то же слово в Sales, Billing и Shipping. В Sales это lead с потенциалом, в Billing — плательщик с tax_id, в Shipping — получатель с адресом. Strategic DDD говорит: это три разных объекта в трёх разных bounded contexts. Не пытайтесь сделать один универсальный — он будет ничей и устроит всех плохо.
«Не ищите одну правильную модель — ищите границы, внутри которых модель согласована. Между границами говорите через интеграцию (events, ACL, published language), а не через shared schema.»
Три уровня:
Subdomain классифицируется по стратегической ценности:
| Тип | Что это | Стратегия |
|---|---|---|
| Core | Конкурентное преимущество | Лучшие инженеры, custom build |
| Supporting | Нужно, но не отличает | Малая команда, можно outsource |
| Generic | Решено всеми | Купить готовое (Stripe, Auth0, SendGrid) |
Bounded context — это граница, где одно слово значит одно. Внутри Sales Customer = lead. Внутри Billing Customer = Payer. Это не «синонимы одного» — это два разных класса в двух разных кодовых базах, связанных событием.
E-commerce домен, разбитый на пять bounded contexts + один legacy. Слева направо в верхнем ряду — три core/supporting контекста: Sales, Billing, Shipping. Каждый со своим сервисом и своей БД (Customer = Lead, Customer = Payer, Customer = Recipient — обратите внимание на лейблы БД, это и есть ubiquitous language в действии).
Нижний ряд — два generic контекста (Auth0, SendGrid) и Legacy CRM (тёмная сторона: Big Ball of Mud, который никто не хочет трогать). Generic подсистемы — это SaaS, который не наш core; их разумно покупать, а не строить.
Линии между группами — это context map. Они подписаны типом интеграции:
sales → billing: Customer/Supplier через событие (CustomerCreated). Sales — upstream, Billing — downstream, есть переговоры о схеме.billing → shipping: Published Language (OrderPaid в стабильном JSON schema). Контракт публичный, любой подписчик может слушать.sales → auth0: Conformist. Auth0 диктует схему user, Sales берёт как есть.billing → sendgrid, sales → legacy-crm: ACL (Anti-Corruption Layer). Внешний хаос транслируется в наш домен.Это не «архитектура приложения», это карта команд и контрактов. Каждая группа — потенциально отдельная команда (Conway's Law).
1. Cross-context flow — один человек, три модели. Customer создаётся в Sales как Lead ({ contact, score, source }), событие CustomerCreated приходит в Billing, который переводит его в Payer ({ tax_id, payment_method }), затем OrderPaid идёт в Shipping, где появляется Recipient ({ address, delivery_window }). Это и есть ключевая идея: один и тот же человек, три разных модели, три разных кодовых базы. Если бы был один универсальный User на 50 полей — каждая команда тащила бы лишнее, изменения требовали бы согласования всех, схема была бы ничей.
2. ACL vs legacy. Sales запрашивает контакт в Legacy CRM. Legacy отвечает чудовищным XML (<Cust><FName>...</FName><CustID_Legacy>X-99</CustID_Legacy></Cust>). Без ACL эта XML-структура расползлась бы по всем классам Sales навсегда. С ACL — слой трансляции превращает XML в чистый Lead { firstName, externalId }, и доменная модель Sales остаётся чистой. ACL стоит ресурсов, но окупается каждой следующей фичей, которая не тонет в legacy-нечисти.
3. Conformist — когда downstream не имеет силы. Sales использует Auth0 user-модель как есть. auth0.user_id становится primary key в Sales. Конформизм здесь — рациональный выбор: Auth0 не будет менять схему ради нас, ACL добавил бы сложности без пользы, generic subdomain стабилен. Conformist — это не лень, это сознательный отказ от переговоров там, где они невозможны и не нужны.
4. Анти-паттерн — микросервис на таблицу. UserService, AddressService, OrderService, OrderLineService — выглядит как «правильные микросервисы». На практике: создание одного заказа = 7 cross-service вызовов, изменение схемы User ломает 4 сервиса в lockstep, «независимые» сервисы деплоятся только все вместе. Это distributed monolith с дополнительной сетевой latency и без преимуществ микросервисов. Фикс: резать по business capability (bounded context), а не по data shape.
За strategic DDD:
Против:
ADR (см. узел sales-svc): применяем strategic DDD когда >2 команд на одной кодовой базе, домен-язык уже дрифтит, продукт ожидается >3 лет, и кросс-командные митинги доминируют календарь. Скипаем для MVP, single-team CRUD, прототипов. Половинное применение (одна граница, без работы над языком) хуже, чем неприменение — получите все недостатки без преимуществ.
User с 50 полями становится ничей, изменения требуют 12 согласований.JOIN-ы.