DDD:本地实体身份应该包括 parent 吗?

DDD: should local Entity identity include the parent's?

在 DDD 中,Entities 具有 identity 的概念,它可以唯一标识每个实例,而不管所有其他属性。通常这个身份在实体所在的BC中必须是唯一的,但有一个例外。

有时我们需要创建聚合,这些聚合不仅由根实体和一些值 Objects 组成,而且具有一个或多个 child / 嵌套实体(据我所知称为 本地实体)。对于这种实体,身份只需要在本地是唯一的,即在聚合边界中是唯一的。

鉴于此,我们还要考虑以下事实:在 DDD 中对 has-a 关系进行建模有两种方式,具体取决于实际业务需求:单独聚合或聚合根 + child 个实体。

在第一种情况下,关系的“child”聚合引用了 parent 的标识,后者通常有一个工厂方法来创建和 return child:

的实例
class ForumId extends ValueObject
{
  // let's say we have a random UUID here
  //  forum name is not a suitable identifier because it can be changed
}

// "parent" aggregate
class Forum extends AggregateRoot
{
  private ForumId _forumId;
  private string _name;

  method startNewThread(ThreadId threadId, string title): Thread
  {
    // make some checks, maybe the title is not appropriate for this forum
    //  and needs to be rejected

    ...

    // passing this forum's ID,
    return new Thread(this->_forumId, threadId, title)
  }
}

class ThreadId extends ValueObject
{
  // let's say we have a random UUID here
  //  thread title is not a suitable identifier because it can be changed
}

// "child" aggregate
class Thread extends AggregateRoot
{
  private ForumId _forumId;
  private ThreadID _threadId;
  private string _title;
}

如果我们考虑第二种情况,比方说,由于某些业务原因,我们需要将 Thread 作为 Forum 的本地实体,识别它的正确方法是什么? Thread 是否仍然包含 parent ForumForumId 还是多余的,因为它只会存在于特定的 Forum 中并且永远不会在外部访问?

哪种方式更好,更重要的是为什么?数据模型(即数据库级别)可能会以这种或另一种方式引导决策,还是我们仍然应该按照良好的 DDD 设计忽略它?

class Forum extends AggregateRoot
{
  private ForumId _forumId;
  private string _name;
  private List<Thread> _threads;

  method startNewThread(string title): ThreadId
  {
    // or use and injected `ThreadIdentityService`'s `nextThreadId(ForumId)` method
    var threadId = this.generateNextLocalThreadId()
    var newThread = new Thread(/*this->_forumId, */ threadId, title)
    this._threads.append(newThread)
    return threadId
  }
}

// "child" aggregate - case 1
class Thread extends LocalEntity
{
  private ForumId _forumId;
  private ThreadID _threadId;
  private string _title;
}

// "child" aggregate - case 2
class Thread extends LocalEntity
{
  private ThreadID _threadId;
  private string _title;
}

所以拥有一个聚合的主要目的是对这个聚合进行原子性的任何更改。 聚合根包含内部的完整子实体,例如论坛将有一个线程集合。 由于线程已经在论坛中,因此将 ForumId 放入其中没有任何意义,因为负责保存它的存储库已经知道该 ID,因为我们将保存整个论坛而不是单个线程。

还想补充一点,论坛聚合似乎是一个巨大的聚合,这意味着您应该考虑一些权衡。