Entity & value object

Несмотря на то, что DDD делает акцент на поведении, без объектов нам не обойтись. DDD выделяет два типа объектов: entities и value objects. Их основные свойства можно свести в следующую таблицу:

Свойство Entity Value object
Идентификация Определяется полем Id Определяется совокупностью всех своих свойств (два vo равны только если все их свойства соответсвенно одинаковы)
Жизненный цикл Бесконечный. Обладает историей (даже если мы ее не храним) изменений. Нулевой, поскольку легко заменяемы: одна рублёвая монета может быть заменена другой. Принадлежат одной или нескольким сущностям.
Изменяемость Изменяемый Неизменяемый (иначе жизненный цикл будет не нулевым)
Ответственность Идентификация и отслеживание жизненного цикла. Всю остальную логику нужно стараться выносить в Value objects Любая логика, не изменяющая сам объект
Как хранятся в БД В отдельных таблицах Вкладывается в таблицу entity, которой принадлежит. Если свойств несколько - вкладываются они все.
Пример Appointment DateTimeRange

Доменный сервис

Доменный сервис должен содержать доменную логику (в отличии от сервиса приложения), которую нельзя отнести ни к одной из entity или value object. Обычно доменный сервис содержит методы, описывающие сложные сценарии, затрагивающие несколько доменных объектов. То есть, доменный сервис дирижирует другими доменными объектами, а не выполняет оперции сам. В противном случае можно “скатиться” к транзакционному программированию и анемичным моделям.

Агрегат

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

Внутри каждого агрегата должен присутствовать корень (aggregate root). Задача корня в том, чтобы поддерживать корректность инвариантов (совокупность возможных значений) всего агрегата. Например, если интернет-магазин не может высылать заказы тяжелее 10 килогамм, то контроль этого требования следует реализовать в корне (заказе).

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

Репозиторий

Репозиторий - это паттерн, который используется далеко за пределами DDD. Его цель - инкапсулировать код работы с хранилищем данных (например, БД) в одном месте. Причем так, чтобы для клиентов репозитория доступ к данным осуществлялся также просто, как если бы они обращались к коллекции в памяти.

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

… и у DDD тоже есть слои

Если наложить основные сущности DDD на onion architecture - то получим такую картину:

onionArchitecture-img

Главное, на что стоит обратить внимание - entities, value objects, domain events и агрегаты находятся в самом центре, а соответственно - не должны иметь внешних зависимостей. Если нам удастся изолировать домен таким образом - то его будет проще понимать и тестировать.