Может ли агрегировать ссылку на корень другого корня? - PullRequest
0 голосов
/ 30 мая 2018

Я немного растерялся.Я только что посмотрел
видео Pluralsight Джулии Лерман на DDD, и вот какая у меня путаница: Пример простого интернет-магазина с: Заказами на покупку с Предметами для Поставщиками Каков суммарный корень здесь?

Технически Заказ на покупку , верно?Это для конкретного поставщика и имеет предметов .Это имеет смысл.

Но ... Является ли Предмет также корнем?У него есть другие «подобъекты», такие как «Бренд», «Дизайнер», «Цвет», «Тип» и т. Д. В вашей системе SOA может быть отдельное приложение для редактирования и управления элементами (без ПО).Так что ... в этом случае вам нужно будет получить доступ к компоненту совокупного корня - что запрещено.

Является ли Item агрегатный корень в этом примере или нет?

Ответы [ 2 ]

0 голосов
/ 31 мая 2018

Это зависит от контекста, в котором вы находитесь. Я попытаюсь объяснить с помощью нескольких различных примеров контекста и в конце ответить на вопрос.

Допустим, первый контекст связан с добавлением новых элементов всистема.В этом контексте Предмет является совокупным корнем.Скорее всего, вы будете создавать и добавлять новые элементы в свое хранилище данных или удалять элементы.Допустим, класс может выглядеть следующим образом:

namespace ItemManagement
{
    public class Item : IAggregateRoot // For clarity
    {
        public int ItemId {get; private set;}

        public string Description {get; private set;}

        public decimal Price {get; private set;}

        public Color Color {get; private set;}

        public Brand Brand {get; private set;} // In this context, Brand is an entity and not a root

        public void ChangeColor(Color newColor){//...}

        // More logic relevant to the management of Items.
    }
}

Теперь предположим, что другая часть системы позволяет составлять заказ на покупку путем добавления и удаления элементов из заказа.В этом контексте Item не только не является совокупным корнем, но в идеале это даже не тот же класс.Зачем?Потому что бренд, цвет и вся логика, скорее всего, будут совершенно неактуальны в этом контексте.Вот пример кода:

namespace Sales
{
    public class PurchaseOrder : IAggregateRoot
    {
        public int PurchaseOrderId {get; private set;}

        public IList<int> Items {get; private set;} //Item ids

        public void RemoveItem(int itemIdToRemove)
        {
            // Remove by id
        }

        public void AddItem(int itemId) // Received from UI for example
        {
            // Add id to set
        }
    }
}

В этом контексте Item представлен только Id.Это единственная значимая часть в этом контексте.Нам нужно знать, какие предметы есть в заказе на покупку.Нам нет дела до бренда или чего-то еще.Теперь вы, наверное, задаетесь вопросом, как узнать цену и описание товаров в заказе на покупку?Это еще один контекст - просмотр и удаление предметов, аналогично многим системам «проверки» в Интернете.В этом контексте у нас могут быть следующие классы:

namespace Checkout
{
    public class Item : IEntity
    {
        public int ItemId {get; private set;}

        public string Description {get; private set;}

        public decimal Price {get; private set;}
    }

    public class PurchaseOrder : IAggregateRoot
    {
        public int PurchaseOrderId {get; private set;}

        public IList<Item> Items {get; private set;}

        public decimal TotalCost => this.Items.Sum(i => i.Price);

        public void RemoveItem(int itemId)
        {
            // Remove item by id
        }
    }
}

В этом контексте у нас очень узкая версия item, потому что этот контекст не допускает изменения Items.Это позволяет только просматривать заказ на покупку и возможность удалять товары.Пользователь может выбрать элемент для просмотра, и в этом случае контекст снова переключается, и вы можете загрузить полный элемент в качестве совокупного корня, чтобы отобразить всю соответствующую информацию.

В случае определения того, есть ли у васфондовый, я думаю, что это еще один контекст с другим корнем.Например:

namespace warehousing
{
    public class Warehouse : IAggregateRoot
    {
        // Id, name, etc

        public IDictionary<int, int> ItemStock {get; private set;} // First int is item Id, second int is stock

        public bool IsInStock(int itemId)
        {
            // Check dictionary to see if stock is greater than zero
        }
    }
}

Каждый контекст через свою собственную версию корня и сущностей предоставляет информацию и логику, необходимые для выполнения своих обязанностей.Не больше и не меньше.

Я понимаю, что ваше реальное приложение будет значительно более сложным, требующим проверки запасов перед добавлением элементов в ПО и т. Д. Дело в том, что в идеале в вашем корне должно быть загружено все, что есть.требуется для завершения функции, и никакой другой контекст не должен влиять на настройку корня в другом контексте.

Итак, чтобы ответить на ваш вопрос - любой класс может быть либо сущностью, либо корнем в зависимости от контекста иесли вы хорошо справились с ограниченным контекстом, ваши корни редко будут ссылаться друг на друга.Вы НЕ ДОЛЖНЫ повторно использовать один и тот же класс во всех контекстах.Фактически, использование одного и того же класса часто приводит к тому, что класс User имеет длину 3000 строк, поскольку в нем есть логика для управления банковскими счетами, адресами, данными профиля, друзьями, бенефициарами, инвестициями и т. Д. Ни одна из этих вещей не принадлежит друг другу.

Чтобы ответить на ваши вопросы

  1. Q: Почему Item AR называется ItemManagement, а PO AR называется просто PurchaseOrder?

Имя пространства имен отражает имя контекста, в котором вы находитесь. Таким образом, в контексте управления элементом Item является корнем и он помещается в пространство имен ItemManagement.Вы также можете думать о ItemManagement как Aggregate , а Item как Root этого агрегата.Я не уверен, что это ответит на ваш вопрос.

Q: Должны ли сущности (например, легкий предмет) иметь методы и логику?

Это полностью зависит от того, о чем вы говорите.Если вы собираетесь использовать Товар только для отображения цен и названий, то нет.Логика не должна быть выставлена, если она не должна использоваться в контексте.В примере контекста Checkout у товара нет логики, поскольку он служит только для того, чтобы показать пользователю, из чего состоит заказ на покупку.Если есть другая функция, в которой, например, пользователь может изменить цвет товара (например, телефона) в заказе на покупку во время оформления заказа, вы можете рассмотреть возможность добавления этого типа логики к товару в этом контексте.

Как AR обращаются к базе данных?Должны ли они иметь интерфейс ... скажем, IPurchaseOrderData, с методом, подобным void RemoveItem (int itemId)?

Я извиняюсь.Я предположил, что ваша система использует какой-то ORM, такой как (N) Hibernate или Entity Framework.В случае такого ORM ORM был бы достаточно умен, чтобы автоматически преобразовывать обновления коллекции в правильный sql, когда корень сохраняется (учитывая, что ваше сопоставление настроено правильно).В случае, когда вы управляете собственной настойчивостью, это немного сложнее.Чтобы ответить на вопрос напрямую - вы можете вставить интерфейс хранилища данных в корень, но я бы посоветовал не делать этого.

У вас может быть хранилище, которое может загружать и сохранять агрегаты.Давайте рассмотрим пример заказа на поставку с элементами в контексте CheckOut.Ваш репозиторий, скорее всего, будет выглядеть следующим образом:

public class PurchaseOrderRepository
{
    // ...
    public void Save(PurchaseOrder toSave)
    {
        var queryBuilder = new StringBuilder();

        foreach(var item in toSave.Items)
        {
           // Insert, update or remove the item
           // Build up your db command here for example:
           queryBuilder.AppendLine($"INSERT INTO [PurchaseOrder_Item] VALUES ([{toSave.PurchaseOrderId}], [{item.ItemId}])");

        }
    }
    // ...
}

А ваш API или уровень сервиса будет выглядеть примерно так:

public void RemoveItem(int purchaseOrderId, int itemId)
{
    using(var unitOfWork = this.purchaseOrderRepository.BeginUnitOfWork())
    {
        var purchaseOrder = this.purchaseOrderRepository.LoadById(purchaseOrderId);

        purchaseOrder.RemoveItem(itemId);

        this.purchaseOrderRepository.Save(purchaseOrder); 

        unitOfWork.Commit();
    }
}

В этом случае ваш репозиторий может стать довольно труднымвоплощать в жизнь.На самом деле может быть проще сделать так, чтобы он удалял элементы в заказе на покупку и повторно добавлял те, которые находятся в корне PurchaseOrder (легко, но не рекомендуется).У вас будет хранилище для каждого агрегатного корня.

Не по теме: ORM-подобный (N) Hibernate будет иметь дело с сохранением (PO), отслеживая все изменения, внесенные в ваш корень с момента его появления.загружен.Таким образом, он будет иметь внутреннюю историю того, что изменилось, и выдает соответствующие команды, чтобы синхронизировать состояние вашей базы данных с вашим корневым состоянием при сохранении с помощью SQL, чтобы адресовать каждое изменение, внесенное в корень и его дочерние элементы.

0 голосов
/ 30 мая 2018

Имея простой пример интернет-магазина с: Заказы на поставку с товарами для поставщиков, каков здесь общий корень?

Это зависит от того, как вы его моделируете, что, в свою очередь, должно зависеть откак вы думаете, информация меняется с течением времени.

Например, одной из возможных моделей было бы объединение всех этих объектов в один агрегат.

Более распространенным вариантом является обработка каждого заказа на покупку отдельно.от других;в этом случае вы, вероятно, сделаете каждый заказ совокупным корнем.Поскольку несколько заказов, вероятно, будут иметь отношения к одному и тому же поставщику, поставщик, вероятно, также является совокупным.

Позиции менее ясны - записи в заказе, вероятно, являются локальными для этого заказа, поэтому менее вероятно, чтовы бы создали отдельную границу согласованности для управления ими.С другой стороны, продукты / скусы, вероятно, будут повторно использоваться несколькими заказами, что снова предполагает, что они являются отдельным агрегатом.

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

Таким образом, мой заказ на покупку (# 12345) может включать «2 единицы товара (# 67890)», ноесли я хочу узнать, что означает , тогда я должен взять продукт (# 67890) и использовать его для поиска остальных данных для продукта.

Если я хочу иметь логику некоторого ПО, например «Делать что-то с товарами, которые есть в наличии», я должен был бы получить все элементы этого ПО и вызвать для них метод IsInStock ().IsInStock - публичный метод Item, поэтому я не нарушаю принципы DDD.Я?

Краткий ответ: нет.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...