Domain-Driven Design (DDD) – это подход к проектированию программного обеспечения, при котором сложная бизнес-логика реализуется через модель предметной области. Для этого DDD предлагает ряд тактических строительных блоков: сущности (Entities), объекты-значения (Value Objects), агрегаты (Aggregates) и доменные сервисы (Domain Services). Рассмотрим каждую из этих составляющих подробно и проиллюстрируем их на примере сервиса бронирования авиабилетов. Особое внимание уделим тому, чем сущность отличается от агрегата, поскольку эти понятия часто путают.
Объект-значение – это объект, не имеющий собственной уникальной идентичности; он полностью определяется совокупностью своих атрибутовhabr.com. Иными словами, два объекта-значения считаются одинаковыми, если у них совпадают все свойства, а не идентификатор. Обычно объекты-значения обладают рядом характерных свойств:
Объекты-значения в коде, как правило, реализуются как простые классы без поля идентификатора. В Python такие объекты удобно создавать с помощью dataclass
(с параметром frozen=True
для иммутабельности). Их свойства задаются через конструктор, а любая бизнес-логика инкапсулируется во внутренних методах. Ниже приведён пример объекта-значения для цены авиабилета:
from dataclasses import dataclass
@dataclass(frozen=True)
class Price:
amount: int # сумма в центах или копейках
currency: str # код валюты, например "USD", "EUR"
def __post_init__(self):
if self.amount < 0:
raise ValueError("Цена не может быть отрицательной")
В этом примере класс Price
содержит два поля: сумму и валюту. Он неизменяемый (frozen=True
), а в методе __post_init__
добавлена проверка инварианта – цена не должна быть отрицательной. Два экземпляра Price
с одинаковыми значениями полей будут считаться равными (библиотека dataclasses
автоматически генерирует метод __eq__
для сравнения по атрибутам). Например, Price(10000, "RUB")
(100 рублей) при сравнении будет равен другому Price(10000, "RUB")
с такими же значениями.
Использование объектов-значений помогает избавиться от «одержимости примитивами» и делать код более выразительнымmedium.com. Вместо передачи разрозненных примитивов (например, строки и числа для валюты и суммы) мы оперируем осмысленным типом Price
. В контексте бронирования авиабилетов объекты-значения могут представлять различные концепции: стоимость билета, временной интервал рейса, маршрут (пара аэропортов отправления и прибытия), контактные данные пассажира и т.д. Такие компоненты не требуют идентификаторов – их ценность именно в данных, которые они несут. Более того, практический опыт показывает, что объекты-значения стоит использовать как можно чаще, поскольку их легче создавать, тестировать и поддерживать по сравнению с сущностямиhabr.com. Если какую-то часть доменной логики можно выразить через значение, а не через сущность, лучше выбрать объект-значение.
Сущность в DDD – это объект, обладающий уникальной идентичностью и непрерывностью жизненного циклаhabr.com. В отличие от объекта-значения, сущность важна не что она из себя представляет в данный момент, а кто она – её личность. Даже если свойства сущности со временем меняются, она остаётся той же самой сущностью благодаря своему идентификатору. Сущности моделируют те понятия предметной области, которые являются уникальными и различными от других объектов. При проектировании сущности первым делом продумывается способ формирования её уникального IDhabr.com – будь то числовой ключ, UUID, осмысленный код или комбинация полей.
Ключевые признаки сущности: