DDD中的Domain模型应该与持久性无关。
CQRS要求我为我的读模型中不想要的一切触发事件。(顺便说一下,把我的模型分成一个写模型和至少一个读模型)。
ES要求我为改变状态的一切触发事件,我的聚合根必须处理事件本身。
这对我来说似乎不是很坚持不可知论。
那么,如何将DDD和CQRS/ES结合起来,而不会对域模型造成严重影响呢?
读模型是否也在DDD域模型中?还是在它之外?
CQRS/ES事件是否与DDD域事件相同?
编辑:
我从答案中得到的是:
是的,ORM领域模型对象的实现将不同于使用ES。问题是错误的方法。首先编写领域模型对象,然后决定如何持久化(更多类似事件=
但我怀疑,你将永远能够使用ES(没有大的添加/更改你的领域对象),如果你没有做出这个决定,也使用ORM没有决定它前面会造成很大的痛苦。:-)
命令
归根结底,CQRS意味着你应该把你的阅读和写作分开。
通常,命令到达系统并由某种函数处理,然后返回该命令产生的零个、一个或多个事件:
handle : cmd:Command -> Event list
现在您有了一个事件列表。您所要做的就是将它们持久化在某个地方。执行此操作的函数可能如下所示:
persist : evt:Event -> unit
但是,这样的持久化函数纯粹是一个基础设施问题。客户端通常只会看到一个将命令作为输入而不返回任何内容的函数:
attempt : cmd:Command -> unit
其余的(句柄
,后跟坚持
)是异步处理的,因此客户端永远不会看到这些函数。
查询
给定一个事件列表,您可以重放它们以将它们聚合成所需的结果。这样的函数本质上看起来像这样:
query : target:'a -> events:Event list -> Result
给定事件列表和要查找的目标(例如ID),这样的函数可以将事件折叠成结果。
执着无知
这是否迫使您使用特定类型的持久性?
这些函数都不是根据任何特定的持久性技术定义的。您可以使用
从概念上讲,它确实迫使您从事件的角度考虑持久性,但这与ORM的方法没有什么不同,ORM迫使您从实体和关系的角度考虑持久性。
这里的重点是很容易将CQRSES体系结构与大多数实现细节分离。这通常是持久的——无知的。
你问题中的许多前提都是非常二进制/黑白的。我不认为DDD、CQRS或事件源是那么规范的——有许多可能的解释和实现。
也就是说,只有一个你的前提困扰着我(强调我的):
ES要求我为改变状态的一切触发事件,我的聚合根必须处理事件本身。
通常AR会发出事件——它们不会处理它们。
在任何情况下,CQRS和ES都可以实现为完全与持久性无关(通常也是)。事件存储为流,可以存储在关系数据库、非关系型数据库、文件系统、内存等中。事件的存储通常在应用程序的边界实现(我认为这是基础设施),领域模型不知道它们的流是如何存储的。
类似地,读模型可以存储在任何可以想象的存储介质中。您可以有10种不同的读模型和投影,每个模型和投影都存储在不同的数据库和不同的格式中。投影只处理/读取事件流,否则与域完全解耦。
没有比这更与持久性无关的了。
不知道这有多正统,但是我有一个当前的事件源实体模型做了这样的事情,这可能说明了区别 . . . (C#示例)
public interface IEventSourcedEntity<IEventTypeICanRespondTo>{
void When(IEventTypeICanRespondTo event);
}
public interface IUser{
bool IsLoggedIn{get;}
}
public class User : IUser, IEventSourcedEntity<IUserEvent>{
public bool IsLoggedIn{get;private set;}
public virtual void When(IUserEvent event){
if(event is LoggedInEvent){
IsLoggedIn = true;
}
}
}
非常简单的例子——但是你可以在这里看到事件的持久化方式(甚至是IF)是在域对象之外的。你可以通过存储库轻松做到这一点。同样,CQRS受到尊重,因为我读取值的方式与设置值的方式是分开的。例如,假设我有一个用户的多个设备,并且只希望它们在超过两个设备时登录?
public class MultiDeviceUser : IUser, IEventSourcedEntity<IUserEvent>{
private IEnumerable<LoggedInEvent> _logInEvents = . . .
public bool IsLoggedIn{
get{
return _logInEvents.Count() > MIN_NUMBER_OF_LOGINS;
}
}
public void When(IUserEvent ev){
if(ev is LoggedInEvent){
_logInEvents.Add(ev);
}
}
}
但是,对于调用代码,您的操作是相同的。
var ev = new LoggedInEvent();
user.When(ev);
if(user.IsLoggedIn) . . . .