Domain driven design DDD:本地实体标识是否应包括母公司';s

Domain driven design DDD:本地实体标识是否应包括母公司';s,domain-driven-design,entity,local,identity,Domain Driven Design,Entity,Local,Identity,在DDD中,实体具有唯一标识每个实例的标识概念,而不考虑所有其他属性。通常,该身份在实体所在的BC中必须是唯一的,但有一个例外 有时我们需要创建聚合,这些聚合不仅由根实体和一些值对象组成,而且还具有一个或多个子/嵌套实体(我理解为称为局部实体)。对于此类实体,标识只需在局部唯一,即在聚合边界中唯一 考虑到这一点,让我们考虑一个事实,即根据实际业务需求,在DDD中建立HAS-A关系的两种方式:单独的聚合或聚合根+子实体。 在第一种情况下,关系的“子”聚合引用父对象的标识,而父对象的标识通常有一个工

在DDD中,实体具有唯一标识每个实例的标识概念,而不考虑所有其他属性。通常,该身份在实体所在的BC中必须是唯一的,但有一个例外

有时我们需要创建聚合,这些聚合不仅由根实体和一些值对象组成,而且还具有一个或多个子/嵌套实体(我理解为称为局部实体)。对于此类实体,标识只需在局部唯一,即在聚合边界中唯一

考虑到这一点,让我们考虑一个事实,即根据实际业务需求,在DDD中建立HAS-A关系的两种方式:单独的聚合或聚合根+子实体。 在第一种情况下,关系的“子”聚合引用父对象的标识,而父对象的标识通常有一个工厂方法来创建和返回子对象的实例:

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

如果我们考虑第二种情况,我们可以说,由于某些商业原因,我们需要将<代码>线程<代码>作为代码<论坛>代码的本地实体,正确的方法是什么?

线程
是否仍应包含父
论坛
ForumId
,或者它是多余的,因为它只存在于特定的
论坛
内,从不在外部访问

哪种方式更好,更重要的是为什么?数据模型(即数据库级)是否可以将决策导向某个方向,或者根据良好的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;
}
类论坛扩展了AggregateRoot
{
私人福鲁米德(ForumId),;
私有字符串\u名称;
私有列表线程;
方法startNewThread(字符串标题):ThreadId
{
//或者使用并注入`ThreadIdentityService`'nextThreadId(ForumId)`方法
var threadId=this.generateNextLocalThreadId()
var newThread=新线程(/*this->\u forumId,*/threadId,title)
此.u threads.append(newThread)
返回线程ID
}
}
//“儿童”合计-案例1
类线程扩展LocalEntity
{
私人福鲁米德(ForumId),;
私有ThreadID _ThreadID;
私有字符串\u标题;
}
//“儿童”合计-案例2
类线程扩展LocalEntity
{
私有ThreadID _ThreadID;
私有字符串\u标题;
}

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


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

是的,这正是我的想法,对我来说似乎是重复!顺便说一句,不要担心大聚合,因为论坛/线程只是一个例子来说明我的问题。感谢您的快速回复!域事件呢?如果我们想让子类引发一个事件,比如说通过将它添加到私有集合中,我如何构造这样一个事件?假设事件是
线程重命名的
,它包含线程的ID和名称。。。但如果没有论坛ID,我们就无法完全识别这一事件,不是吗?所以这似乎暗示了一种从孩子那里获取父母ID的方法。。。或者孩子不应该提出自己的事件?但这似乎不正确……因为入口点是聚合根,我将把这个逻辑放在聚合根本身中,这样它也可以注册这个事件,因为为了保持原子性,任何人都不能直接访问子实体。所以在本例中,我们将调用Forum->renameThread(thread\u id,new\u thread\u name),这样子/本地实体应该只有标识,而没有“事件管理”。。。但是,如果对父对象的操作导致对每个子对象执行方法,该怎么办?子级的事件添加逻辑现在将在两个方法中复制。。。人为的例子,但我们可以关闭一个特定的线程,这会引发一个ThreadClosed事件,我们可以关闭论坛和have ForumClosed,但也有很多AdClosed事件,现在我们在
forum::closeThread
forum::close
方法中都有了
::addDomainEvent(ThreadClosed)
,如果在你的情况下,它更适合从子实体发起活动,那么我不会把它作为强制性规则,我这样做是因为在我的情况下很容易。在您的方法中,您可以使用聚合根pull events方法,该方法可以迭代所有子实体以获取其域事件,也可以在对子实体执行操作后将域事件拉入子实体,并将其保存到聚合根中的events集合。不确定我是否解释得足够好:)