Domain-Driven Design

От бизнес-стратегии к коду через домены

19 апреля 2026

Справочник

Domain-Driven Design помогает связать модель предметной области с кодом. Стратегический дизайн уточняет термины, границы моделей и их связи; тактические паттерны помогают реализовать эти решения. В статье разбираю обе части и их применение в существующей системе. Для работы с агентами особенно полезны записанные термины и бизнес-правила: по ним можно уточнять задачу и проверять реализацию.

Что такое DDD и чего он не делает

Domain-Driven Design предлагает строить модель вместе с людьми, которые понимают предметную область. Термины и правила бизнеса отражаются в коде, а границы контекстов помогают разделять модели. Как эти границы соотносятся с модулями и командами, зависит от устройства продукта.

DDD состоит из двух частей:

Распространенное заблуждение: DDD = тактика. Команда читает Evans, узнает про агрегаты и репозитории, начинает размечать классы. В коде появляются знакомые паттерны, но команда не договаривается о терминах и границах модели.

Тактика работает только поверх стратегии. Без стратегии агрегаты - просто классы с методами, репозитории - обертки над ORM, единый язык - глоссарий, который никто не читает.

Для простых CRUD-задач полное доменное моделирование может оказаться избыточным. Объем моделирования выбирают по сложности правил и цене ошибок. DDD не заменяет бизнес-стратегию и не требует микросервисной архитектуры.

Поддомены и конкурентное преимущество

Предметная область компании не единое целое. Это набор зон. В каких-то зонах бизнес зарабатывает, в каких-то - поддерживает инфраструктуру работы. DDD разделяет на три типа.

Основной поддомен (core) связан с конкурентным преимуществом компании. Например, это может быть скоринг в финтехе или подбор предложений на маркетплейсе. Какая часть действительно относится к core, нужно определять для конкретного бизнеса. Здесь особенно важны доменная экспертиза и возможность менять реализацию по мере изменения задач.

Универсальный поддомен (generic) - индустриальный стандарт. Зона с зрелыми готовыми решениями. Аутентификация, биллинг через Stripe, email через SendGrid, CMS. Собственная реализация generic-поддомена требует оценки затрат: даже если напишете собственную платежную платформу, в нее придется заложить все, что Stripe уже сделал, плюс поддерживать десятилетиями.

Вспомогательный поддомен (supporting) - нужен для работы бизнеса, но не дает преимущества. Админ-панели, внутренние инструменты, системы отчетности, HR. Кандидаты на: готовые продукты, аутсорсинг или CRUD-приложения с минимальными усилиями внутри.

Центральный стратегический вопрос: где наше конкурентное преимущество, и куда инвестировать лучших инженеров. Если этот вопрос не задан, DDD не работает. Команда распределяет ресурсы равномерно, core получает внимание наравне с generic, преимущество разбавляется.

Практически - для каждой зоны ответить:

Core получает сильных инженеров. Generic закрывается интеграцией. Supporting закрывается минимальными средствами.

Единый язык (Ubiquitous Language)

Единый язык формируют разработчики и эксперты предметной области внутри bounded context. В разных контекстах одинаковое слово может обозначать разные модели. Поэтому нужно явно указывать, в каком контексте используется термин, и отражать эту договоренность в коде и документации.

Без единого языка: бизнес говорит «клиент», продукт говорит «пользователь», бэкенд называет User, БД хранит в accounts, интеграция с платежкой называет customer. Каждая команда в переводе теряет смысл. Через полгода никто не помнит, что «клиент» в разговоре CEO - не то же самое, что User, потому что компания добавила B2B-клиентов, а User - это конечный пользователь внутри организации-клиента.

Единый язык живет в:

Чтобы согласовать язык, бизнес и инженеры вместе описывают предметную область, находят противоречия и договариваются о терминах. Для этого можно использовать событийное моделирование.

Выигрыш не только в ясности. Изменения в бизнесе (новая политика, сегмент, продукт) легче транслируются в код: новое понятие получает имя, имя появляется в модели, API, документации - без перевода и искажений.

Ограниченные контексты (Bounded Contexts)

Одна модель на всю предметную область не работает. «Клиент» в биллинге - подписчик с тарифом и платежами. «Клиент» в маркетинге - лид с источником трафика и стадией воронки. «Клиент» в support - тикет-автор. Если поместить это в один Customer, класс может стать большим и трудным для изменения.

Ограниченный контекст (bounded context) - явная граница, внутри которой модель и язык однозначны. Разные контексты могут использовать одно слово для разных понятий, и это нормально.

Карта контекстов (context map) показывает какие контексты существуют, что термины значат в каждом, как контексты связаны, какие паттерны взаимодействия.

Стратегические паттерны взаимодействия

Карта контекстов - стратегический документ. По нему видно, где команды работают плотно, где независимо, где legacy блокирует развитие, где нужен ACL.

DDD как мост от стратегии к коду и командам

DDD часто воспринимают как инструмент для «красивой архитектуры»: слои, интерфейсы и паттерны. В этой статье DDD рассматривается прежде всего как способ связать задачи бизнеса с моделью в коде и распределением ответственности команд.

Цепочка:

Бизнес-стратегия определяет, где компания зарабатывает и куда движется. Где конкурентное преимущество, какие сегменты важнее, какие направления растут.

DDD переводит стратегию в модель предметной области. Core-поддомены (где преимущество) получают глубокое моделирование, единый язык, границы. Generic и supporting - минимальная проработка. Карта контекстов показывает связи.

Team Topologies распределяет команды по bounded contexts. Как рабочая эвристика Stream-aligned команда владеет контекстом целиком: язык, модель, код, эксплуатация. Соответствие не обязано быть 1:1 - одна команда часто владеет несколькими небольшими контекстами, а крупному контексту может понадобиться несколько команд. Подробнее в статье «Team Topologies».

Архитектура - последняя остановка. Границы контекстов становятся границами модулей и сервисов. Язык - именами в коде и API. Способы взаимодействия - интеграционными паттернами. Как рабочая эвристика архитектура следует из предметной области, а не выбирается отдельно от нее. На практике связь двусторонняя: архитектура и стратегия влияют друг на друга итеративно, и существующие технические ограничения (унаследованные системы, регуляторные требования, задержки) иногда диктуют выбор независимо от домена.

Порядок важен. Если начать с архитектуры («нам нужны микросервисы»), получается технически красивая система, плохо отражающая бизнес. Если начать с команд («наймем N инженеров, распределим по фичам»), команды привязаны к случайным границам. Если начать с бизнес-стратегии, но не пройти DDD-шаг, переход к командам и архитектуре идет на ощупь.

Моделирование требует времени. Его пользу можно оценивать по тому, насколько согласованная модель упрощает дальнейшие изменения в коде и работу команд.

Перевод DDD в архитектуру

Стратегический дизайн остается теорией без перевода в код. Что обязательно сохраняется:

Границы контекстов. Реализация должна сохранять понятные границы моделей и публичные способы взаимодействия. Bounded context не обязан совпадать с отдельным сервисом: в одном приложении могут находиться несколько контекстов.

Единый язык → имена в коде, API, БД. Термин Subscription в модели - это класс Subscription в коде, endpoint /subscriptions, таблица subscriptions. Нет перевода. Нет SubscriptionDTO vs SubscriptionModel с разным смыслом. Нет БД, где это customer_plans, а в коде - Subscription.

Anti-Corruption Layer. Слой перевода полезен, когда внешняя модель не должна определять внутреннюю. Его необходимость зависит от отношений между контекстами: DDD также описывает Partnership, Conformist и другие варианты взаимодействия.

Доменные события. Событие описывает факт в предметной области. Его можно использовать для связи между контекстами, но DDD не требует заменять событиями все вызовы. Синхронное API и события выбирают по требованиям к ответу, согласованности и обработке сбоев.

Частый сбой: модель в документации одна, в коде другая. Команда обсуждает «клиент с подпиской», код оперирует User и Account. Новый инженер читает код, строит у себя модель User + Account, начинает разговаривать в этих терминах. Через полгода все говорят двумя разными языками в зависимости от собеседника. Восстановление в этой точке дороже, чем поддержание с начала.

Защитить терминологию помогает дисциплина в code review. PR, в котором появляется имя, не соответствующее единому языку, - повод для обсуждения. Либо имя приводится к языку, либо язык расширяется сознательно.

DDD и AI-агенты: почему домен должен быть явным

Агент использует доступные ему код, документацию и инструменты. Устные договоренности и знания участников не попадут в его контекст автоматически; важные для задачи решения нужно передать явно.

Если в задаче неясно, что означает «клиент», агент может спросить об этом или сделать предположение. Процесс работы должен предусматривать уточнения и проверку предположений человеком. Описание контекста и терминов уменьшает неоднозначность, но не гарантирует правильную реализацию.

Поэтому терминология и бизнес-операции теперь - часть спецификации. Без DDD спека выглядит как «сделай что-то с клиентом, чтобы получилось X». Агент выбирает собственную модель «клиента» - обычно ту, что чаще встречается в обучающих данных, или ту, что кажется подходящей по контексту файла. Получается работающий код, не соответствующий бизнес-модели.

С DDD спека дает семантику. Bounded context фиксирует, в какой части домена работаем. Ubiquitous language определяет термины. Инварианты агрегата сообщают, какие правила нарушать нельзя. События описывают, что происходит в других контекстах при этом изменении. Агент следует семантике, не изобретает.

Spec Kit разделяет описание требований и план реализации. Это можно использовать для фиксации терминов и контекста задачи. Такое разделение не делает DDD обязательным условием использования инструмента.

Практический вывод: размытая доменная модель мешает не только архитектуре и коммуникации, но и внедрению агентов. Попытка вести агентскую разработку поверх неявной модели дает некорректный код быстрее, чем без агентов: агент воспроизводит имеющуюся путаницу на скорости. С явной моделью агент следует ей; без нее - додумывает.

Тактические паттерны

Инструменты реализации модели в коде. Работают только поверх стратегии.

Сущность (Entity) - отражение объекта реального мира, имеющего идентичность, которая сохраняется через время. Идентичность позволяет отличить один экземпляр от другого: два заказа с одинаковой суммой и одинаковыми позициями - все равно разные заказы, если у них разные номера. Две сущности с одинаковыми атрибутами - разные, если разные id.

Сущность живет внутри одного bounded context и работает только для него. Один бизнес-объект может существовать в нескольких контекстах с общей идентичностью, но разным содержанием. Заказ с номером ORDER-12345:

Общий номер связывает контексты, но внутри каждого Order - разная сущность со своими атрибутами и поведением. Попытка вместить все атрибуты в один класс - путь к тысячестроковому Order, непонятному никому. Разделение на bounded contexts решает это явно: общий id связывает, модели различаются.

Агрегат - граница согласованности и транзакционной целостности, сформированная из одной или нескольких сущностей вместе с их объектами-значениями. В простых случаях агрегат и сущность совпадают один к одному: сущность User - это и есть агрегат User. В сложных - агрегат составной: корневая сущность плюс набор дочерних, которые меняются только вместе с корневой. Пример агрегата с несколькими сущностями: Order с коллекцией OrderLine - позиции нельзя менять независимо от заказа, иначе ломаются инварианты (суммы, допустимые комбинации, состояние оплаты).

Ключевое свойство: сущности внутри агрегата изменяются только через корневую сущность и только в составе транзакции над агрегатом целиком. Это делает границу агрегата явной зоной согласованности: внутри инварианты обеспечиваются кодом, снаружи - другой агрегат с собственными правилами.

Границы агрегата помогают определить, какие инварианты нужно сохранять при изменениях. Если несколько процессов меняют один агрегат, нужно выбрать способ управления конкуренцией, например оптимистичную или пессимистичную блокировку. Выбор зависит от частоты конфликтов, хранилища и стоимости повторной операции. Изменения между агрегатами требуют отдельного решения о согласованности.

Доменный сервис выражает операцию предметной области, которую неудобно разместить в сущности или объекте-значении. Название и интерфейс должны соответствовать языку контекста. Это не требование создавать отдельный класс для каждой пользовательской операции.

Общее поведение стоит выделять там, где совпадает бизнес-смысл. Различия между контекстами и сценариями не нужно скрывать в одном сервисе только ради повторного использования кода.

При этом разные бизнес-операции не должны схлопываться в одну, не отражающую бизнес-смысл. Антипаттерн - сервис UpdateOrder, одновременно отвечающий за обновление позиций в заказе, прием платежа, обработку доставки на склад. Каждая из этих операций - отдельная бизнес-сущность со своими инвариантами, событиями и, возможно, разными командами-владельцами. Слияние в один сервис теряет семантику и ломает выравнивание с языком бизнеса.

Пример: перевод денег между двумя аккаунтами - TransferMoney domain service. Он оркестрирует два агрегата (списание с одного, начисление на другой), обеспечивает согласованность, публикует событие. Сервис отражает отдельную бизнес-операцию, которая затрагивает несколько агрегатов.

Объект-значение (Value Object) - объект, описывающий что-то, но не имеющий собственной идентичности. Противоположность Entity: Entity отвечает на вопрос «кто это» или «что это конкретно», VO - на вопрос «какое оно».

Два VO с одинаковыми атрибутами - одинаковы, это одно и то же значение. $100 USD - это $100 USD, независимо от того, сколько раз создается в коде. Адрес «Москва, Тверская 7» - один и тот же адрес, даже если сохранен у двух разных клиентов. Сравнение с Entity: две сущности с одинаковыми атрибутами - все равно разные, если разные id.

VO иммутабельны: изменение атрибута означает создание нового VO, не модификацию существующего. Это следует из отсутствия идентичности - если у объекта нет id, «измененный объект» и есть уже другой объект с другим значением.

Примеры: Money (amount + currency), Address, DateRange, Email, PhoneNumber, Coordinates. Использование VO вместо примитивов защищает от ошибок: нельзя случайно сложить 100 USD и 100 EUR, если Money - VO с валютой; нельзя передать произвольную строку туда, где ждут Email; нельзя перепутать дату начала и дату окончания периода, если для периода есть DateRange.

Практическое правило. Если в коде встречаются несколько параметров, всегда идущих вместе и связанных общим смыслом (amount + currency, latitude + longitude, street + city + zip) - это кандидат в VO. Без VO связанные параметры передаются отдельно, поэтому при изменении функции легче потерять связь между ними.

Доменное событие - факт, произошедший в домене, значимый для бизнеса. SubscriptionCancelled, PaymentReceived, OrderShipped. Иммутабельны, содержат данные для обработки участниками.

Это один из самых важных паттернов DDD. На уровне реализации систем события обеспечивают отказоустойчивость и изолированность: публикующий контекст не ждет синхронного ответа, фиксирует факт и продолжает работу. Потребители обрабатывают события в своем темпе, могут упасть и восстановиться без потери данных (если события персистируются).

События дают возможность масштабировать контексты независимо: производитель не связан с количеством и скоростью потребителей. Добавление нового потребителя - подписка на существующие события, без изменений в производителе.

Событие - контракт между публикующим и потребляющими контекстами. Формат становится stable-API, как REST-endpoint или gRPC-схема. Изменение требует либо обратной совместимости, либо согласования с потребителями.

Это уменьшает зависимость контекстов друг от друга во время выполнения. Два контекста, взаимодействующих через события, перестают быть runtime-зависимыми друг от друга: падение потребителя не валит производителя. Логическая связанность остается (потребитель ожидает конкретную семантику события), но контролируется через версионирование и явный контракт.

Хороший признак в системе: большинство межконтекстных взаимодействий идут через события. Это сигнал, что домены разделены правильно и действительно отражают независимые части бизнеса. Каждый контекст автономен, контракты явные.

Плохой признак: много взаимодействий спрятано внутри бизнес-операций - синхронные цепочки вызовов между контекстами. Это превращается в сложные взаимодействия, особенно когда контексты разделяются на сервисы и требуется graceful degradation: приходится изобретать circuit breakers, таймауты, фоллбэки на каждый вызов, потому что runtime coupling не был убран на уровне модели.

Второй плохой признак: бесконечные SAGA с компенсирующей логикой на каждый шаг. SAGA оправдана для распределенных транзакций, но когда она становится основным паттерном взаимодействия - это симптом неправильного деления на агрегаты или контексты. Попытка достичь изоляцию через компенсации на каждый чих дает экспоненциальный рост сложности: отслеживать состояние, обрабатывать частичные падения, тестировать все пути компенсации - все это быстро выходит из-под контроля.

Реализационные паттерны. Следующие элементы ближе к особенностям реализации: они формируют технический слой между доменной моделью и инфраструктурой. Многие напрямую поддерживаются фреймворками и языками, и их «название в DDD» часто совпадает с понятием из стека.

Эти паттерны полезно знать, но не обязательно реализовывать руками. Фреймворк обычно дает 60-80% из коробки: ORM покрывает Repository и Unit of Work, native-конструкторы работают как Factory, встроенный dispatcher - события. Важно различать доменные паттерны (Aggregate, Entity, VO, Domain Service, Domain Event - смысл идет от бизнеса) и реализационные (Factory, Repository, UoW, Specification, Application Service - смысл технический). Первые выбираются по бизнес-необходимости, вторые - по удобству стека.

Ограничение: тактика не обязательна. Она применяется, когда доменная сложность это оправдывает. Для CRUD-формы - избыточна. Для core-поддомена с сложными бизнес-правилами - окупается.

Как разные архитектуры реализуют DDD

DDD не предписывает одну архитектуру. Доменная модель может жить в разных структурах кода, каждая со своими компромиссы. Ниже - как ключевые элементы DDD (entity, aggregate, repository, domain service) укладываются в основные архитектурные паттерны.

Слоистая архитектура (Layered / N-Tier)

Структура: представление → прикладной слой → доменная модель → слой доступа к данным. Каждый слой зависит от нижележащего.

Как ложится DDD: агрегаты и сущности - в доменном слое; repository - на границе доменного и data access; application services - в прикладном слое; domain services - в доменном.

Проблема: доменный слой зависит от data access (вниз). Репозиторий напрямую связан с ORM или базой, что затрудняет юнит-тестирование модели без БД и замену инфраструктуры.

Когда подходит: CRUD-приложения с тонкой бизнес-логикой. Rails-приложения по умолчанию - это слоистая архитектура, где ActiveRecord совмещает data access и entity. Работает, пока логика простая.

Когда ломается: доменные правила усложняются, требуется независимое тестирование модели, нужна гибкость в замене инфраструктуры (смена ORM, БД, добавление новых источников данных).

Порты и адаптеры (Hexagonal / Onion / Clean Architecture)

Структура: доменная модель в центре. Вокруг - порты (интерфейсы, определенные доменом). Снаружи - адаптеры: БД-адаптер, HTTP-адаптер, event-bus-адаптер, реализующие порты.

Как ложится DDD: агрегаты и сущности - ядро. Repository-интерфейс - порт в домене, ORM-реализация - адаптер снаружи. Application services - на границе порта и адаптера. Domain services - в ядре.

Ключевое отличие от слоистой: зависимости инвертированы. Инфраструктура зависит от домена, не наоборот. Домен не знает, что сохраняется в PostgreSQL или MongoDB.

Три названия одной идеи: Hexagonal (Alistair Cockburn, 2005) подчеркивает симметрию портов; Onion (Jeffrey Palermo, 2008) - концентрические слои от домена к инфраструктуре; Clean (Robert Martin, 2012) - конкретные названия слоев (entities, use cases, interface adapters, frameworks).

Когда подходит: сложный домен, долгоживущее приложение, где инфраструктура может меняться. DDD проще всего реализовать на этой архитектуре.

Цена: больше абстракций, больше кода для той же функциональности. Для простого CRUD - overkill.

CQRS (Command Query Responsibility Segregation)

Структура: две модели в одном bounded context. Модель записи (command side) - сложные агрегаты с инвариантами, обрабатывают изменения. Модель чтения (query side) - денормализованные проекции, оптимизированы под конкретные запросы.

Как ложится DDD: write-модель - классические DDD-агрегаты со всей тактикой (Aggregate, Entity, VO, Domain Service). Read-модель - простые структуры данных без бизнес-логики, возвращаются в UI напрямую.

Синхронизация: командная модель публикует доменные события, проекции слушают и обновляются. Могут быть синхронными (в той же транзакции) или асинхронными (eventually consistent).

Когда подходит: требования к чтению сильно отличаются от записи. Запись - сложная бизнес-логика на пяти связанных агрегатах; чтение - десять разных дашбордов с денормализацией из этих агрегатов. Без CQRS либо read-модели «размазываются» по write, либо read-запросы становятся дорогими join-ами.

Цена: две модели требуют поддержки, синхронизация добавляет сложность. Частая ловушка - «давайте CQRS» как самоцель; правильный триггер - доказанная разница в требованиях к записи и чтению.

Event Sourcing

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

Как ложится DDD: естественное расширение доменных событий. Каждое изменение агрегата - событие в event store. Агрегат восстанавливается из истории; команды генерируют новые события.

Преимущества: История событий может использоваться для аудита, пересчета проекций и восстановления состояния на выбранный момент. Возможность отменять операции нужно проектировать отдельно с учетом правил предметной области.

Цена: все сложнее. Схема событий - контракт, который нельзя просто изменить (старые события никуда не денутся). Event versioning, snapshots для больших агрегатов, миграции событий, репроекции - все это операционно нагружает команду. Debug сложнее: для понимания текущего состояния нужно проиграть события.

Когда подходит: аудит критичен (финансы, медицина, compliance), бизнес оперирует историей как первичной сущностью, temporal queries - требование продукта, не прихоть. Без бизнес-необходимости - много сложности без соразмерной выгоды.

Модульный монолит (Modular Monolith)

Структура: один deployment-артефакт, но с явными границами модулей по bounded contexts. Каждый модуль - отдельная кодовая единица с собственной доменной моделью, которая может ничего не знать о других модулях.

Как ложится DDD: bounded context = модуль. Каждый модуль использует свою архитектурную схему (слоистая, hexagonal, CQRS) в зависимости от сложности. Коммуникация между модулями - через события или публичные API модуля, не через shared database.

Когда подходит: команда 15-50 инженеров с 3-10 bounded contexts. Проще операционно, чем микросервисы; дает явные границы, которых не дает традиционный монолит. Подробнее в статье «Монолит vs Микросервисы».

Эвристика выбора: начать с простого (слоистая для CRUD, hexagonal для сложной доменной логики). Модульный монолит - хороший default для команды средней зрелости. CQRS и event sourcing - только при явной потребности, подтвержденной требованиями продукта. Ретроспективное упрощение сложной архитектуры дороже эволюции простой.

DDD и микросервисы: распространенная ловушка

Утверждение «bounded context = микросервис» часто в презентациях. В строгом смысле это не так.

Правильная формулировка: безопасная граница для микросервиса - bounded context. Если вы решаете делить на микросервисы, делить надо по bounded contexts. Обратное не верно: bounded context не обязан быть микросервисом.

Bounded context может быть отдельным микросервисом, модулем внутри монолита, подсистемой внутри модульного монолита. Выбор определяется:

Подробнее в статье «Монолит vs Микросервисы».

Ловушка - начать с «нам нужны микросервисы, давайте найдем bounded contexts для деления». Обратный порядок. Сначала bounded contexts выявляются (из DDD-анализа). Потом для каждого решается - микросервис, модуль, или часть монолита.

Алгоритм внедрения DDD

Подход зависит от стартовой позиции. Три типовых сценария с разными входными условиями: greenfield с бизнес-вовлечением, brownfield с бюджетом на выправление, и ситуация без формальной поддержки бизнеса.

Научно-популярный: greenfield с бизнес-вовлечением

Идеальный вариант: новый проект или существенная перестройка с бюджетом и явной поддержкой бизнеса. Можно пройти полный цикл от моделирования до реализации.

  1. Event storming с бизнесом. Собрать команду (инженеры + продукт + бизнес), выписывать события предметной области - «что случается» в бизнесе. Без технических деталей.
  2. Группировка событий в поддомены. События кластеризуются по тематике. Каждая группа - кандидат в поддомен. Обозначить тип: core, generic, supporting.
  3. Единый язык для каждого потенциального контекста. Какие термины встречаются, зафиксировать значения. Пересечения - признак, что нужны bounded contexts с разным значением одного слова.
  4. Границы контекстов. Где граница одного контекста и начало другого. Обычно проходят там, где меняется значение термина или где событие одного контекста становится входом другого.
  5. Карта контекстов. Визуализировать контексты и связи. Обозначить паттерны (Customer-Supplier, ACL и т. д.).
  6. Тактическое моделирование для core. Агрегаты, VO, доменные сервисы. Для generic и supporting - минимум, часто CRUD.
  7. Отражение в архитектуре. Границы - в модулях и сервисах. Язык - в именах. ACL - где нужно.
  8. Отражение в командах. Как правило, каждый core-контекст - ответственность одной Stream-aligned команды; в нашем случае это рабочий default, но команда может владеть и несколькими небольшими контекстами. Generic - интеграция. Supporting - отдельная команда или shared capacity.
  9. Итерация. Модель эволюционирует с бизнесом. Карта - живой документ, обновляется при изменениях.

Event storming не обязателен - один из способов. Альтернативы: domain storytelling, user story mapping с событиями, совместные сессии с бизнесом. Важен процесс, не инструмент.

Прагматический: существующая система, нужно выправлять

Система уже выросла органически, терминология смешана, границы размыты. Есть бюджет на умеренные улучшения, но полностью переписывать нельзя. Работа идет параллельно с продуктовой разработкой.

Подпольный: без бизнес-buy-in, но проблемы решать надо

Ценность DDD для бизнеса доказать не удалось, формального бюджета нет, но проблемы отсутствия модели реальны: регрессии, долгий onboarding, запутанный код, страх трогать биллинг. Двигаться все равно можно, но осторожно.

Подпольный вариант не идеален, но часто единственно возможен на первых этапах. Главное ограничение - одинокий инженер не может пере-сделать систему целиком; подпольная версия работает только там, где есть хотя бы небольшая зона локального контроля и возможность показать результат.

Почему DDD последнее, что выкидывается при упрощении

Регулярно на ретроспективах или в разговорах про упрощение возникает вопрос: что можно убрать. В моем опыте DDD - последнее, что убирается.

Причина простая. Предметная область - место, где компания зарабатывает деньги. Все остальное - стек, процессы, инструменты, конкретные архитектурные решения - сервисное. Стек меняется, процессы эволюционируют, фреймворки устаревают. Доменная модель, если правильно отражает бизнес, переживает и стек, и фреймворки.

Критично выравнивание в обе стороны:

Когда выравнивание есть, рост не требует переизобретать модель каждые полгода. Когда его нет, начинается деградация: термины в коде расходятся с бизнесом, архитектура перестает отражать бизнес-зоны, команды работают над фичами, не понимая, к какой зоне те относятся. Восстановление дороже поддержания.

Что можно упростить безопасно:

Что трудно убрать без последствий:

Стратегический DDD - ubiquitous language, bounded contexts, карта контекстов - выживает при любом упрощении. Тактика и конкретные архитектурные решения - кандидаты на урезание первыми.

Частые ошибки и антипаттерны

Источники и смежные статьи

Первоисточники

Связанные темы

Смежные статьи блога