DDD:聚合和实体关系

DDD: Aggregate and entities relationship

我阅读了一些关于 DDD 的出版物和话题。关于聚合和实体之间的联系有很多声音。我知道聚合应该尽可能简单(每个聚合一个实体)。但是聚合有实体集合的情况呢?

比方说,我们有一个名为“Month”的聚合,其中包含一组“Day”对象(它们是域实体,因为它们需要一个身份才能 可区分 - 让聚合知道要修改哪个“日”。

所以我有两个问题:

  1. 这是正确的方法吗?只是正常情况,我不应该担心?
  2. 外面的“能见度”怎么样?在我的方法中,聚合是“包私有的”,不允许任何人在系统的不同部分使用它。 但是实体呢?它们是否应该像系统不同部分的值对象一样可见?或者只是创建另一个VO来表示外部的实体(例如:当实体存储在事件中时)?

谢谢大家的回答

聚合、实体和值对象

在我看来,一个实体是一个 child,其名称 (Id) 是一个 Aggregate,其中,反过来,是 父亲 有一个名字 (Id)。

聚合不能有实体。 你可以认为很好是一个实体,不:一个实体只存在于一个聚合中。

实体(child)是一个集合体(父亲),没有其他实体(children's)。

爸爸们和children可以用透明盒子(ValueObject的概念我没有更好的翻译,sorry):当你创建一个透明框你不能改变任何东西但你可以阅读内容,如果你想更新你必须创建一个新的框。

聚合负责管理实体,这意味着:添加、更新、删除和查询。

如果您想与一个或多个实体交谈(查询),您必须询问聚合,因此,当您加载聚合时,您必须加载所有实体。

一个聚合可以有多种类型的实体,有多少种?好吧,这取决于你,取决于设计,也取决于系统。

显然,许多字段和许多实体的大聚合可能效率不高,在这种情况下,也许您可​​以选择最大的实体并在有或没有 child 的情况下上交任.

实例

我在 csharp 上写了示例,但与 Java 没有太大区别。


class Invoice : ValueObject
{
    public string Number { get; private set; }

    public DateTime Date { get; private set; }

    public decimal TaxableAmount { get; private set; }

    public decimal VatAmount { get; private set; }

    public decimal TotalAmount => TaxableAmount + VatAmount;

    public Invoice(string number, DateTime date, decimal taxableAmount, decimal 
         vatAmount)
    {
         // validation
         Number = number;
         [..]
    }

}

class Taxonomy : Entity 
{
   public int Id {get; private set;}

   public decimal Amount {get; private set;}

   public string Classfication {get; private set;}

   public Taxonomy(int id, decimal amount, string classification)
   {
      // validation
      Id = id;
      [..]
   }
}


class SaleAggregate : AggregateRoot
{
   private List<Taxonomy> _taxonomies;

   public int Id {get; private set;}
   
   public Invoice Invoice {get; private set;}

   public IReadOnlyCollection<Taxonomy> Taxonomies => _taxonomies.AsReadOnly();

   public SaleAggregate(int id, string number, DateTime date, decimal 
           taxableAmount, decimal vatAmount)
   {
       _taxonomies = new List<Taxonomy>();
    
       // I prefer to pass ALWAYS primitive types to not rely on valueObject
       // validation
       Id = id;
       Invoice = new Invoice(number, date, taxableAmount, vatAmount)
       [..]
   }

   public void AddTaxonomy(int id, decimal amount, string classification)   
   {
       // validation
       _taxonomies.Add(new Taxonomy(id, amount, classification);
   }

   public void UpdateTaxonomy(int id, decimal amount, string classification)
   {
       // validation
       var entity = _taxonomies.FirstOrDefault(p=> p.Id == id);
       entity.Amount = amount;
       entity.Classification = classification;
   }

   public void RemoveTaxonomy(int id)
   {
       // validation
       var entity = _taxonomies.FirstOrDefault(p=> p.Id == id);
       _taxonomy.Remove(entity);
   }

   public void UpdateVatAmount(decimal vatAmount)
   {
       // validation
       Invoice = new Invoice(Invoice.Number, Invoice.Date, 
                    Invoice.TaxableAmount, vatAmount);
   }
}


再次声明:这是我对聚合、实体和值对象的看法,阅读本文的其他开发人员可以随时纠正我。

将日和月建模为实体在很大程度上取决于上下文。它可能不是解释聚合和实体的最佳示例,但让我们试一试。

让我们假设在我们的上下文中一天不能单独存在。必须在一个月之内。如果要引用一天,则必须先指定月份。这就是我们在现实生活中使用日期的方式,1 月 1 日、5 月 8 日……尽管这些日子是实体,但它们不需要全局唯一标识符。他们只需要 [1 .. 31].

月份内的标识符

聚合应尽可能小,但每个聚合不应只包含一个实体并不是规定。您只需要有一个在所有系统中具有唯一标识符的聚合根(月)。在聚合中,您可以拥有在聚合 [1 .. 31] 中具有唯一标识符的实体(天)。如果您想引用或访问这些实体,您应该始终通过聚合根。