Инженерия ПО

Domain-Driven Design

Цели урока

  • Понимать, что DDD решает не код, а коммуникацию между бизнесом и инженерами
  • Знать ключевые тактические паттерны: Entity, Value Object, Aggregate, Domain Event
  • Видеть Bounded Context как границу одной модели данных, а не один сервис
  • Использовать Ubiquitous Language в коде, тестах и обсуждениях с бизнесом
  • Распознавать ситуации, где DDD оправдан (сложный домен), и где он избыточен (CRUD)

Предварительные знания

  • Базовое ООП: классы, инкапсуляция, наследование
  • Понимание SOLID и базовых архитектурных паттернов
  • Опыт работы над приложением с непростой бизнес-логикой (банкинг, e-commerce, логистика)

2003 год: Эрик Эванс публикует 'Domain-Driven Design'. 560 страниц о том, как код должен отражать бизнес-реальность, а не наоборот. 20 лет спустя DDD стал языком архитектурных разговоров в Amazon, Google, Netflix. Bounded Context - это не просто концепция, это граница между командами. Conway's Law: структура системы отражает структуру организации. DDD помогает сделать эту структуру явной и осознанной.

  • **Amazon**: каждый сервис соответствует Bounded Context - Orders, Payments, Inventory, Recommendations - со своей командой, моделью и language
  • **Uber**: Driver контекст и Rider контекст имеют разные модели Trip - для водителя это маршрут и заработок, для пассажира это время и цена
  • **Shopify**: модель Product в Storefront контексте (SEO, фото, описание) принципиально отличается от Product в Fulfillment контексте (SKU, вес, склад)

Bounded Context

Bounded Context (ограниченный контекст) - явная граница, внутри которой доменная модель последовательна и имеет единый язык (Ubiquitous Language). Эрик Эванс в книге DDD 2003 года: одна и та же концепция может означать разные вещи в разных контекстах. 'Customer' в контексте продаж - лид с воронкой сделок. 'Customer' в контексте поддержки - тикеты и SLA. 'Customer' в контексте биллинга - подписки и платежи. Попытка создать единую Customer модель для всех контекстов создаёт монстра.

Context Map показывает отношения между контекстами: Shared Kernel (два контекста разделяют часть модели), Customer-Supplier (upstream контекст влияет на downstream), Anti-Corruption Layer (ACL защищает от загрязнения внешней моделью). Microservices часто соответствуют Bounded Context один к одному, но это не требование - один контекст может быть реализован несколькими сервисами или наоборот.

E-commerce система имеет одну большую модель Product, используемую в каталоге, складе и биллинге. Какую проблему это создаёт по DDD?

Aggregates

Aggregate - кластер объектов (Entity и Value Objects), который обрабатывается как единица консистентности. Aggregate Root - единственная точка входа для изменений агрегата. Правило: внешние объекты могут хранить ссылку только на Aggregate Root, но не на его внутренние сущности. Это обеспечивает инварианты (бизнес-правила) всегда соблюдены внутри агрегата.

Размер агрегата влияет на производительность и конкурентность. Большой агрегат (Order с 1000 items) - одна транзакция блокирует всё, много конфликтов при параллельном доступе. Маленькие агрегаты - меньше конфликтов, быстрее. Правило Вернона: 'если сомневаешься - делай агрегат меньше'. Если два агрегата часто изменяются вместе - возможно они один агрегат. Если редко - возможно eventual consistency достаточно.

Внешний сервис хочет обновить цену OrderItem напрямую через его ID (без обращения к Order). Почему это нарушает DDD?

Entities и идентичность

Entity - объект, чья идентичность определяется не атрибутами, а уникальным ID. Два объекта Customer с одинаковым именем и email - разные Entity если у них разные ID. Атрибуты меняются (имя, адрес, email), но идентичность остаётся. В базе данных Entity соответствует строке с первичным ключом. Lifecycle: создание, изменение состояния, возможно удаление.

Repository pattern - абстракция для хранилища агрегатов. Repository принимает и возвращает полностью сформированные агрегаты, скрывая детали хранения. Принцип: один Repository на один Aggregate Root. OrderRepository сохраняет Order вместе с OrderItems - одной операцией, не по отдельности. Это обеспечивает атомарность транзакции по границам агрегата.

OrderRepository.save() получает Order агрегат и сохраняет его. Как должна работать транзакция?

Value Objects

Value Object - объект, чья идентичность определяется совокупностью атрибутов, не ID. Два Money(100, USD) объекта равны, потому что их атрибуты равны - нет смысла различать 'этот доллар' от 'того доллара'. Value Objects иммутабельны: вместо изменения создаётся новый объект. Это устраняет целый класс багов с неожиданными мутациями через shared references.

Примеры Value Objects: Money (amount + currency), Address (street, city, country), Email, PhoneNumber, DateRange, Coordinates. Всё что описывает 'что это такое', а не 'кто это' - кандидат в Value Object. Преимущества: можно передавать и возвращать без опасения мутации, equals() работает через структурное сравнение, содержат поведение связанное с данными (Money.add(), DateRange.overlaps()).

DDD применяется только для микросервисов - в монолите он избыточен

DDD применяется независимо от физической архитектуры. Bounded Contexts можно реализовать как модули в монолите (Modular Monolith) - это часто правильный первый шаг перед разбивкой на сервисы

Ценность DDD - в явных границах и Ubiquitous Language, а не в физическом разделении. Стартовать с Modular Monolith по DDD проще: нет сетевых вызовов, нет распределённых транзакций, те же концептуальные границы

Что отличает Entity от Value Object в DDD?

Ключевые идеи

  • **Bounded Context**: явная граница с единым языком - одна концепция (Customer) означает разное в разных контекстах, и это нормально
  • **Aggregate**: кластер объектов как единица консистентности, Aggregate Root - единственная точка изменения, транзакция = граница агрегата
  • **Entity vs Value Object**: Entity идентифицируется по ID (кто), Value Object - по значению (что) и иммутабелен

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

DDD определяет концептуальную архитектуру для многих паттернов:

  • Event Sourcing и CQRS — Domain Events - механизм интеграции между Bounded Contexts в DDD; CQRS решает проблему разных моделей для чтения и записи
  • Монолит vs Микросервисы — Bounded Context - единица разбивки: каждый микросервис должен соответствовать одному контексту или нескольким тесно связанным

Вопросы для размышления

  • Как определить правильный размер Aggregate - слишком большой создаёт проблемы с конкурентностью, слишком маленький - сложность координации?
  • Ubiquitous Language требует постоянного диалога между разработчиками и бизнесом. Как организовать этот процесс в реальной команде?
  • Когда DDD overhead оправдан, а когда простая CRUD модель лучше?

Связанные уроки

  • db-01-intro
Domain-Driven Design

0

1

Войти