Это зависит от контекста, в котором вы находитесь. Я попытаюсь объяснить с помощью нескольких различных примеров контекста и в конце ответить на вопрос.
Допустим, первый контекст связан с добавлением новых элементов всистема.В этом контексте Предмет является совокупным корнем.Скорее всего, вы будете создавать и добавлять новые элементы в свое хранилище данных или удалять элементы.Допустим, класс может выглядеть следующим образом:
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 строк, поскольку в нем есть логика для управления банковскими счетами, адресами, данными профиля, друзьями, бенефициарами, инвестициями и т. Д. Ни одна из этих вещей не принадлежит друг другу.
Чтобы ответить на ваши вопросы
- 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, чтобы адресовать каждое изменение, внесенное в корень и его дочерние элементы.