gpt4 book ai didi

c# - 聚合根可以引用另一个根吗?

转载 作者:行者123 更新时间:2023-12-02 06:08:59 25 4
gpt4 key购买 nike

我有点困惑。我刚看了
Julie Lerman 关于 DDD 的 Pluralsight 视频,这是我的困惑:
有一个简单的在线商店示例:
采购订单商品 供应商 ,这里的聚合根是什么?

技术上 采购订单 , 对?专供供应商并有 项目 在上面。那讲得通。

但是.. Item 也是聚合根吗?它还有其他“子对象”,如“品牌”、“设计师”、“颜色”、“类型”等……您的 SOA 系统中可能有一个单独的应用程序来编辑和管理项目(没有 PO)。所以..在这种情况下,您将不得不访问聚合根的组件 - 这是不允许的。

项目 在这个例子中聚合根与否?

最佳答案

这取决于您所处的上下文。我将尝试用几个不同的上下文示例进行解释,并在最后回答问题。

假设第一个上下文是关于向系统添加新项目。在这种情况下,项目是聚合根。您很可能会在数据存储中构建和添加新项目或删除项目。假设该类可能如下所示:

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
}
}
}

每个上下文,通过其自己版本的根和实体,公开其履行职责所需的信息和逻辑。不多也不少。

我知道您的实际应用程序会复杂得多,需要在将项目添加到 PO 等之前进行库存检查。重点是,理想情况下,您的根目录应该已经加载了完成功能所需的所有内容,并且没有其他上下文应该在不同的上下文中影响根的设置。

因此,要回答您的问题 - 根据上下文,任何类都可以是实体或根,如果您已经很好地管理了有界上下文,那么您的根将很少需要相互引用。您不必在所有上下文中重用相同的类。事实上,使用同一个类通常会导致像 User 类长达 3000 行这样的东西,因为它具有管理银行帐户、地址、个人资料详细信息、 friend 、受益人、投资等的逻辑。这些东西都不属于一起。

回答您的问题
  • 问:为什么 Item AR 称为 ItemManagement 而 PO AR 仅称为 PurchaseOrder?

  • 命名空间名称反射(reflect)了您所在的上下文的名称。因此在项目管理的上下文中,Item 是根,它被放置在 ItemManagement 命名空间中。您也可以将 ItemManagement 视为 聚合 和项目为 这个集合。我不确定这是否回答了您的问题。
  • 问:实体(如轻项)是否也应该有方法和逻辑?

  • 这完全取决于你的上下文是关于什么的。如果您打算仅将 Item 用于显示价格和名称,则不。如果逻辑不应在上下文中使用,则不应公开。在 Checkout 上下文示例中,Item 没有逻辑,因为它们仅用于向用户显示采购订单的组成。如果有一个不同的功能,例如,用户可以在结账期间更改采购订单上项目(如电话)的颜色,您可能会考虑在该上下文中的项目上添加这种类型的逻辑。
  • AR如何访问数据库?他们应该有一个接口(interface)..让我们说IPurchaseOrderData,使用像void RemoveItem(int itemId)这样的方法吗?

  • 我道歉。我假设您的系统正在使用某种 ORM,例如 (N)Hibernate 或 Entity Framework 。在这种 ORM 的情况下,ORM 将足够智能,可以在根持久化时自动将集合更新转换为正确的 sql(假设您的映射配置正确)。
    在您管理自己的持久性的情况下,它稍微复杂一些。要直接回答这个问题 - 您可以将数据存储接口(interface)注入(inject)根目录,但我建议不要。

    您可以拥有一个可以加载和保存聚合的存储库。让我们以 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();
    }
    }

    在这种情况下,您的存储库可能会变得非常难以实现。删除采购订单上的项目并重新添加采购订单根目录上的项目实际上可能更容易(简单但不推荐)。
    每个聚合根都有一个存储库。

    题外话:
    像 (N)Hibernate 这样的 ORM 将通过跟踪自加载以来对根所做的所有更改来处理 Save(PO)。因此,当您通过发出 SQL 来解决对根及其子项所做的每个更改进行保存时,它将具有已更改内容的内部历史记录并发出适当的命令以使您的数据库状态与根状态同步。

    关于c# - 聚合根可以引用另一个根吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50597171/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com