Domain-Driven Design (DDD) – это подход к проектированию программного обеспечения, при котором сложная бизнес-логика реализуется через модель предметной области. Для этого DDD предлагает ряд тактических строительных блоков: сущности (Entities), объекты-значения (Value Objects), агрегаты (Aggregates) и доменные сервисы (Domain Services). Рассмотрим каждую из этих составляющих подробно и проиллюстрируем их на примере сервиса бронирования авиабилетов. Особое внимание уделим тому, чем сущность отличается от агрегата, поскольку эти понятия часто путают.

Объекты-значения (Value Objects)

Объект-значение – это объект, не имеющий собственной уникальной идентичности; он полностью определяется совокупностью своих атрибутов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. Если какую-то часть доменной логики можно выразить через значение, а не через сущность, лучше выбрать объект-значение.

Сущности (Entities)

Сущность в DDD – это объект, обладающий уникальной идентичностью и непрерывностью жизненного циклаhabr.com. В отличие от объекта-значения, сущность важна не что она из себя представляет в данный момент, а кто она – её личность. Даже если свойства сущности со временем меняются, она остаётся той же самой сущностью благодаря своему идентификатору. Сущности моделируют те понятия предметной области, которые являются уникальными и различными от других объектов. При проектировании сущности первым делом продумывается способ формирования её уникального IDhabr.com – будь то числовой ключ, UUID, осмысленный код или комбинация полей.

Ключевые признаки сущности: