带孙子的 .NET DDD 域模式

.NET DDD Domain Pattern with Grandchildren

我想在我的 .NET Core 域模型中实现 DDD 模式。 以下是具有聚合子实体和聚合孙实体的聚合根示例:

public class Supplier
    : Entity, IAggregateRoot
{
    private string _name;

    private readonly List<Catalog> _catalogs;
    public IReadOnlyCollection<Catalog> Catalogs => _catalogs;

    protected Supplier() { _catalogs = new List<Catalog>(); }

    public Supplier(string name)
    {
        _catalogs = new List<Catalog>();
        _name = name;
    }

    public void AddCatalog(string name)
    {
        var catalog = new Catalog(name);
        _catalogs.Add(catalog);
    }

    public void AddCatalogItem(int catalogId, string name)
    {
        var catalogItem = new CatalogItem(name);
        _catalogs.Single(c => c.Id == catalogId).AddCatalogItem(catalogItem);
    }
}

这里是聚合子项的代码:

public class Catalog
    : Entity
{
    private string _name;

    private readonly List<CatalogItem> _catalogItems;
    public IReadOnlyCollection<CatalogItem> CatalogItems => _catalogItems;

    protected Catalog() { _catalogItems = new List<CatalogItem>(); }

    public Catalog(string name)
    {
        _catalogItems = new List<CatalogItem>();
        _name = name;
    }

    public void AddCatalogItem(CatalogItem catalogItem)
    {
        _catalogItems.Add(catalogItem);
    }
}

和聚合孙子:

public class CatalogItem
    : Entity
{
    private string _name;

    protected CatalogItem() { }

    public CatalogItem(string name)
    {
        _name = name;
    }
}

这是完成 DDD 模式的正确方法吗?
或者这是否违反了我不知道的 DDD 规则?

它本身并没有违反任何 DDD 规则 - 但它是一种经常被劝阻的方法,原因如下:

  • DDD 鼓励您根据系统需要支持的操作、流程和规则对域进行建模,同时管理状态的更改
    • 您建模的关系可能存在于现实世界中 - 它们可能有助于可视化数据 - 但它们对于您的模型真的是必需的吗?
  • DDD 中的良好做法是设计小型 聚合。聚合实际上是一个 'consistency' 边界——你把你的一致性边界设置得越大,你就会有一些后果:
    • 大多数操作不涉及所有数据 - 但所有数据在每个操作期间都必须事务锁定
    • 在协作的多用户域中,这可能导致并发问题,其中任一用户由于他们想要操作的数据被不必要地锁定而被阻止
  • 为什么 可能 这种关系对建模有用?
    • 如果您有一个规则(不变的)必须在所有子聚合中具有事务一致的数据,例如:
      • 每个供应商必须最多有 4 个目录 - 要执行此规则,您必须在供应商中具有交易一致的目录列表
      • 目录中目录商品的价格总和不得超过 $X - 同样,您需要所有商品以交易一致的方式

备选方案

那么 - 如果您没有任何此类规则,并且您已被说服创建 'small' 聚合,您可以做什么?

  • 每个实体都成为它自己的集合体
  • Link 个实体,来自 reference。在这种情况下:
    • 供应商不会有目录列表
    • 目录会有一个'SupplierId'到link的目录给供应商,方便供应商获取目录列表
    • Catalog 不会 有 CatalogItem
    • 的列表
    • CatalogItem 将有一个 'CatalogId' 到 link 到目录 - 同样,有助于按目录获得项目列表

这种方法的含义是您将不会在聚合之间具有事务一致性 - 但除非您的规则要求它,否则您不需要它,那么为什么要强制执行它?

您真的需要事务一致性吗?

DDD 鼓励的另一条建议是挑战对事务一致性的需求——这真的有必要吗?

使用上面的简单示例 - 如果供应商最终有 5 个目录会怎样?世界会崩溃吗?即使您确实需要该规则,它是否需要在事务上保持一致?使用较小的聚合,您可以:

  • 如果供应商已经有 4 个目录,则包括客户端验证以防止操作
  • 创建目录时发布事件,触发检查和补偿操作 - 如果处理程序确定供应商现在有 5 个目录,则发出警报或更新供应商状态,直到用户删除目录,或自动使最旧的目录过期 - 在您的域中最有意义的任何内容

总结

建议是:

  • 设计小骨料
  • 拥抱最终一致性