Как я должен обеспечить соблюдение отношений и ограничений между совокупными корнями?

У меня есть пара вопросов относительно отношений между ссылками между двумя агрегатными корнями в модели DDD. Обратитесь к типичной модели клиента / заказа, приведенной ниже.

Во-первых, должны ли ссылки между фактической реализацией объектов агрегатов всегда выполняться через значения идентификаторов, а не ссылки на объекты? Например, если мне нужны подробности о клиенте Заказа, мне нужно взять CustomerId и передать его в ICustomerRepository, чтобы получить Customer, а не настраивать объект Order для прямого возврата Customer? Я сбит с толку, потому что возвращение Customer напрямую выглядит так, как будто это облегчит написание кода для модели, и его не так сложно настроить, если я использую ORM, такой как NHibernate. И все же я уверен, что это нарушит границы между совокупными корнями / хранилищами.

Во-вторых, где и как должен применяться каскад отношений удаления для двух совокупных корней? Например, я хочу, чтобы все связанные заказы были удалены при удалении клиента. Метод ICustomerRepository.DeleteCustomer() не должен ссылаться на IOrderRepostiory, не так ли? Кажется, что это нарушило бы границы между агрегатами / хранилищами? Должен ли я вместо этого иметь службу CustomerManagment, которая обрабатывает удаление клиентов и связанных с ними заказов, которые будут ссылаться как на IOrderRepository, так и на ICustomerRepository? В таком случае, как я могу быть уверен, что люди знают об использовании Сервиса, а не о хранилище для удаления Клиентов. Это только из-за обучения их правильному использованию модели?

2 ответа

Во-первых, должны ли ссылки между агрегатами всегда выполняться через значения идентификаторов, а не фактические ссылки на объекты?

Не совсем - хотя некоторые могут внести это изменение по соображениям производительности.

Например, если мне нужны подробности о клиенте Заказа, мне нужно взять CustomerId и передать его в ICustomerRepository, чтобы получить Customer, а не настраивать объект Order для прямого возврата Customer?

Как правило, вы бы смоделировали 1 сторону отношений (например, Customer.Orders или же Order.Customer) для прохождения. Другой может быть получен из соответствующего репозитория (например, CustomerRepository.GetCustomerFor(Order) или же OrderRepository.GetOrdersFor(Customer)).

Разве это не значит, что OrderRepository должен был бы что-то знать о том, как создать Клиента? Разве это не было бы за то, что OrderRepository должен нести ответственность за...

OrderRepository будет знать, как использовать ICustomerRepository.FindById(int), Вы можете ввести ICustomerRepository, Некоторым это может показаться неудобным, и они решили поместить его на уровень обслуживания - но я думаю, что это излишне. Нет особой причины, по которой репозитории не могут знать и использовать друг друга.

Я сбит с толку, потому что возвращение Customer напрямую выглядит так, как будто это облегчит написание кода для модели, и его не так сложно настроить, если я использую ORM, такой как NHibernate. И все же я уверен, что это нарушит границы между совокупными корнями / хранилищами.

Совокупные корни могут содержать ссылки на другие совокупные корни. На самом деле, что угодно может содержать ссылку на совокупный корень. Совокупный корень не может содержать ссылку на неагрегированный корневой объект, который ему не принадлежит.

Например., Customer не может содержать ссылку на OrderLines - поскольку OrderLines должным образом принадлежит как сущность на Order совокупный корень.

Во-вторых, где и как должен применяться каскад отношений удаления для двух совокупных корней?

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

При этом, хотя я не думаю, что это чисто DDD, большинство людей допускают некоторую снисходительность в следовании образцу единицы работы, где вы удаляете Orderс, а затем Customer (который потерпит неудачу, если Orderвсе еще существовало). Вы могли бы даже иметь CustomerRepository делайте работу, если хотите (хотя я бы предпочел, чтобы она была более явной). Также допустимо позволить сиротам Orderбудет убрано позже (или нет). Вариант использования имеет все значение здесь.

Должен ли я вместо этого иметь службу CustomerManagment, которая обрабатывает удаление клиентов и связанных с ними заказов, которые будут ссылаться как на IOrderRepository, так и на ICustomerRepository? В таком случае, как я могу быть уверен, что люди знают об использовании Сервиса, а не о хранилище для удаления Клиентов. Это только из-за обучения их правильному использованию модели?

Я, вероятно, не пошел бы по пути обслуживания для чего-то настолько тесно связанного с хранилищем. Что касается того, как убедиться, что сервис используется... вы просто не ставите публичный Delete на CustomerRepository, Или вы выбрасываете ошибку при удалении Customer оставил бы сиротами Orders.

Другим вариантом может быть создание объекта ValueObject, описывающего связь между Орденом и AR клиента, VO, который будет содержать CustomerId и дополнительную информацию, которая может вам понадобиться - имя, адрес и т. Д. (Что-то вроде ClientInfo или CustomerData).

Это имеет несколько преимуществ:

  1. Ваши AR отделены - и теперь могут быть разделены, сохранены как потоки событий и т. Д.
  2. В AR Заказа вам обычно необходимо хранить информацию о клиенте, которая была у вас на момент создания заказа, и не отражать в ней какие-либо будущие изменения, внесенные в клиента.
  3. Почти во всех случаях информации в объекте значения будет достаточно для выполнения операций чтения (отображение информации о клиенте вместе с заказом).

Чтобы справиться с удалением / деактивацией Клиента, у вас есть свобода выбора любого поведения, которое вам нравится. Вы можете использовать DomainEvents и опубликовать событие CustomerDeleted, для которого у вас может быть обработчик, который перемещает Заказы в архив или удаляет их или все, что вам нужно. Вы также можете выполнить более одной операции над этим событием.

Если по какой-либо причине DomainEvents не является вашим выбором, вы можете сделать операцию Delete реализованной как операцию службы, а не как операцию хранилища, и использовать UOW для выполнения операций на обоих AR.

Я видел много подобных проблем, когда пытался сделать DDD, и я думаю, что источником проблем является то, что разработчики / разработчики моделей имеют тенденцию думать в терминах БД. У вас (у нас:)) есть естественная тенденция к устранению избыточности и нормализации модели предметной области. Как только вы преодолеете это и позволите своей модели развиваться и вовлечь эксперта (специалистов) в своей эволюции, вы увидите, что это не так сложно и вполне естественно.

ОБНОВЛЕНИЕ: и подобный VO - OrderInfo может быть размещен в AR клиента, если это необходимо, только с необходимой информацией - сумма заказа, количество элементов заказа и т. Д.

Другие вопросы по тегам