Обзор DDD: Domain-Driven Design

Domain-Driven Design (DDD) — подход к разработке сложных систем, фокусирующийся на создании модели предметной области, отражающей бизнес-логику. Узнайте, как DDD помогает строить гибкие и масштабируемые приложения с четким разделением ответственности.

DDD Domain-Driven Design

В современном мире разработки программного обеспечения сложность проектов постоянно растёт. Бизнес-логика становится всё более запутанной, а требования заказчиков нередко меняются в процессе работы. Как в таких условиях создавать приложения, которые будут понятны, масштабируемы и легко адаптируемы? Ответом на этот вызов стал подход Domain-Driven Design (DDD), предложенный Эриком Эвансом в 2003 году. DDD — это не просто набор технических приёмов, а философия разработки, которая ставит предметную область (домен) в центр процесса.

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

Такой подход позволяет сократить недопонимание, упростить проектирование сложных систем и сделать их более гибкими. В этой статье мы разберём ключевые концепции DDD, такие как ограниченные контексты, агрегаты и события предметной области, а также покажем, как они применяются на практике. Если вы хотите научиться строить приложения, которые "говорят" на языке бизнеса, DDD — это то, что вам нужно.

Основные концепции DDD

1. Убиквитарный язык (Ubiquitous Language) 

Общий язык, который используется всеми участниками проекта (разработчиками, аналитиками, бизнес-экспертами) для описания домена. Это позволяет избежать недопонимания между техническими и нетехническими сторонами. Например, если бизнес называет клиента "пользователь", то в коде и документации тоже используется "пользователь".

2. Предметная область (Domain) 

Это сфера, которую решает программное обеспечение (например, интернет-магазин, система бронирования билетов). DDD делит сложные домены на поддомены, чтобы управлять ими по отдельности.

3. Ограниченный контекст (Bounded Context) 

Четкие границы, в которых определённая модель домена действует. Это помогает избежать путаницы, когда одно и то же понятие (например, "Заказ") имеет разные значения в разных частях системы (например, заказ в интернет-магазине и заказ в складской системе).

4. Агрегаты 

Группы объектов, рассматриваемых как единое целое. Например, заказ в интернет-магазине (агрегат) может включать товары, адрес доставки и клиента. Агрегат управляется через корневую сущность (Aggregate Root).

5. Сущности и объекты-значения (Entities и Value Objects) 

  • Сущности — объекты с уникальной идентичностью (например, клиент с ID). 
  • Объекты-значения — объекты без идентичности, описывающие характеристики (например, адрес клиента).

6. Репозитории 

Объекты, которые предоставляют доступ к данным (например, загрузка сущностей из базы данных). Они абстрагируют работу с хранилищем, позволяя сосредоточиться на доменной логике.

7. События предметной области (Domain Events) 

События, которые описывают важные изменения в домене (например, "Заказ создан"). Они помогают сделать систему более гибкой и слабосвязанной.

Зачем нужен DDD?

DDD особенно полезен для сложных проектов с богатой бизнес-логикой. Преимущества:

  • Чёткость: Модель отражает реальный бизнес, что упрощает общение между разработчиками и заказчиками.
  • Гибкость: Ограниченные контексты позволяют изменять одну часть системы, не затрагивая другие.
  • Масштабируемость: DDD упрощает добавление новых функций в сложные системы.
  • Тестируемость: Модель домена отделена от инфраструктуры, что упрощает модульное тестирование.

Однако DDD требует затрат времени на анализ домена и создания модели, поэтому он не всегда оправдан для простых приложений (например, CRUD-приложений).

Теория, когда DDD очень необходим

  • Сложная бизнес-логика: Когда приложение решает задачу с глубокими и запутанными бизнес-правилами, требующими точного моделирования. 
  • Долгосрочные проекты: Если система будет развиваться и поддерживаться годами, DDD обеспечивает гибкость и масштабируемость. 
  • Тесное взаимодействие с бизнесом: Когда нужно выстроить общий язык между разработчиками и бизнес-экспертами для минимизации недопонимания. 
  • Микросервисная архитектура: DDD помогает чётко разделить систему на ограниченные контексты, соответствующие микросервисам. 
  • Частые изменения требований: DDD упрощает адаптацию системы к новым бизнес-правилам благодаря модульной структуре. 
  • Командная работа над доменом: Когда несколько команд работают над разными частями системы, DDD позволяет изолировать контексты. 
  • Высокая потребность в тестируемости: DDD изолирует бизнес-логику от инфраструктуры, упрощая написание тестов. 
  • Системы с событийной моделью: Когда бизнес-процессы связаны с событиями (например, "Заказ создан"), DDD поддерживает событийно-ориентированный подход.

Практика, когда DDD очень необходим

  • Интернет-магазин: Сложная логика заказов, управления складом, скидок, доставки и персонализации требует чёткого моделирования домена и разделения контекстов (продажи, склад, платежи). 
  • Банковские системы: Управление счетами, транзакциями, кредитами и рисками с множеством бизнес-правил и строгой согласованностью. 
  • Системы бронирования: Например, бронирование авиабилетов или отелей, где нужна обработка сложных правил доступности, тарифов и отмен. 
  • CRM-системы: Управление клиентами, их взаимодействиями, историей сделок и аналитикой требует глубокого понимания домена. 
  • Логистические платформы: Оптимизация маршрутов, управление складами и трекинг доставки с множеством взаимосвязанных процессов. 
  • Страховые системы: Обработка полисов, расчёт премий, управление рисками и выплатами с учётом сложных бизнес-правил. 
  • Медицинские информационные системы: Управление пациентами, записями, назначениями и диагностикой, где важна точность и согласованность. 
  • Платформы для мероприятий: Продажа билетов, управление местами и расписанием с учётом различных сценариев и ограничений.

Простой пример использования DDD: Интернет-магазин

Сценарий: Разрабатываем систему для интернет-магазина, где клиенты могут заказывать товары. Рассмотрим, как DDD помогает в этом.

1. Убиквитарный язык 

Команда договаривается о терминах. Например, "Заказ" — это то, что клиент оформляет в корзине, а не заявка на складе. Термин "Заказ" используется в коде, документации и обсуждениях.

2. Ограниченный контекст 

Система делится на два контекста: 

  • Контекст продаж: Управляет заказами клиентов, корзиной, оплатой. 
  • Контекст склада: Управляет инвентаризацией и доставкой. 

В каждом контексте "Заказ" имеет разные атрибуты. Например, в продажах заказ включает данные клиента, а на складе — информацию о комплектации.

3. Агрегат: Заказ 

Заказ — это агрегат с корневой сущностью Order. Он включает: 

  • Список товаров (OrderItem — объект-значение). 
  • Данные клиента (Customer — сущность). 
  • Адрес доставки (Address — объект-значение). 

Например, изменение заказа (добавление товара) происходит только через корень Order, чтобы обеспечить согласованность данных.

4. Репозиторий 

Репозиторий (OrderRepository) предоставляет доступ к заказам, абстрагируя взаимодействие с базой данных. Например, метод OrderRepository.getById(orderId) возвращает заказ по его идентификатору, а OrderRepository.save(order) сохраняет заказ. Это позволяет доменной логике сосредоточиться на бизнес-правилах, а не на SQL-запросах.   

5. События предметной области (Domain Events) 

Когда клиент оформляет заказ, создаётся событие, например, OrderCreated. Это событие может уведомить склад о необходимости подготовить товары или отправить письмо клиенту. События делают систему слабосвязанной, позволяя разным контекстам (продажи, склад) взаимодействовать асинхронно.

Пример: 

  • После оформления заказа публикуется событие OrderCreated. 
  • Служба склада подписывается на это событие и резервирует товары.

6. Реализация бизнес-логики

Предположим, есть правило: "Клиент не может заказать товар, если его нет на складе". В DDD это правило реализуется в доменной модели, а не в слое инфраструктуры. Например, в классе Order метод addItem проверяет доступность товара через сервис домена, связанный со складом.

Обоснование использования DDD в этом примере

1. Чёткость бизнес-логики 

Модель заказа (Order) отражает реальные бизнес-правила (например, проверка наличия товара). Это упрощает понимание кода и его соответствие требованиям бизнеса.

2. Разделение ответственности 

Ограниченные контексты (продажи и склад) позволяют командам работать независимо. Если склад изменит свою логику, это не сломает систему продаж.

3. Гибкость изменений 

Добавление новой функции, например, скидок на заказы, легко реализуется в доменной модели без переписывания инфраструктурного кода.

4. Тестируемость 

Бизнес-логика в классе Order изолирована от базы данных или внешних сервисов, что упрощает написание юнит-тестов. Например, можно протестировать метод addItem, подставив заглушку для InventoryService.

Итог

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