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