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

У меня есть пара вопросов относительно взаимосвязи между ссылками между двумя совокупными корнями в модели DDD. См. Типичную схему заказа клиента/заказа, приведенную ниже.

enter image description here

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

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

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

Это имеет ряд преимуществ:

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

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

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

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

UPDATE: и аналогичный VO-OrderInfo может быть размещен внутри AR-клиента, если это необходимо, только с необходимой информацией - количеством заказов, количеством пунктов заказа и т.д.