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

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

enter image description here

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

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

Ответы [ 2 ]

9 голосов
/ 25 мая 2011

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

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

Например, если мне нужны подробности о клиенте Заказа, мне нужно взять 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 должен быть вашим единственным совокупным корнем.В большинстве реальных систем, однако, мы не будем на самом деле удалять a Customer, которые связаны с Order с - мы можем их деактивировать, перемещать их Order s в объединенный Customerи т. д. - но не извлекайте и удаляйте Order s.

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

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

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

1 голос
/ 25 мая 2011

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

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

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

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

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

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

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

...