DDD和CQRS / ES不会破坏DDD的持久性不可知性吗?

时间:2015-06-30 15:03:13

标签: domain-driven-design cqrs event-sourcing

DDD中的域模型应该是持久性不可知的。

CQRS要求我为我在阅读模型中不会拥有的一切事件发起事件。 (顺便把我的模型分成写模型和至少一个读模型)。

ES要求我为所有改变状态的事件触发事件,并且我的聚合根必须处理事件本身。

这对我来说似乎并不是非常持久的。

那么DDD和CQRS / ES如何在没有这种持久性技术对域模型的重大影响的情况下进行组合呢?

读取模型是否也在DDD域模型中?或者在它之外?

CQRS / ES事件是否与DDD域事件相同?

修改

我从答案中取出的内容如下:

是的,对于ORM,域模型目标的实现将与使用ES的实现不同。 问题是错误的方式。首先编写域模型对象,然后决定如何持久化(更多事件,如=> ES,更多数据,如=&; ORM,......)。

但我怀疑你是否能够使用ES(如果你没有对你的域对象进行大的添加/更改),如果你没有做出这个决定,并且使用ORM而没有决定它的前面会导致非常多痛。 : - )

4 个答案:

答案 0 :(得分:6)

<强>命令

归结为它的本质,CQRS意味着你应该从你的写作中分离你的读物。

通常,命令到达系统并由某种函数处理,然后该函数返回由该命令产生的零个,一个或多个事件:

handle : cmd:Command -> Event list

现在您有一个事件列表。你所要做的只是将它们坚持到某个地方。执行此操作的功能可能如下所示:

persist : evt:Event -> unit

但是,这样的 persist 功能纯粹是基础设施问题。客户端通常只会看到一个以Command作为输入并且不返回任何内容的函数:

attempt : cmd:Command -> unit

其余的(handle,后跟persist)是异步处理的,因此客户端永远不会看到这些函数。

<强>查询

给定一个事件列表,您可以重放它们,以便将它们聚合成所需的结果。这样的功能基本上看起来像这样:

query : target:'a -> events:Event list -> Result

给定事件列表和要查找的目标(例如ID),这样的函数可以将事件折叠成结果。

坚持无知

这会强制您使用特定类型的持久性吗?

这些功能都不是根据任何特定的持久性技术定义的。您可以使用

实现这样的系统
  • 内存列表
  • 演员
  • 活动商店
  • 文件
  • 斑点
  • 数据库,甚至

从概念上讲,它确实迫使你根据事件来考虑持久性,但这与使用ORM的方法没有什么不同,这些方法迫使你根据实体和关系来考虑持久性。

这里的要点是,将CQRS + ES架构与大多数实现细节分离起来非常容易。这通常是持久的 - 无知的足够的

答案 1 :(得分:3)

你问题中的许多前提呈现为二进制/黑白。我不认为DDD,CQRS或事件采购是规范性的 - 有许多可能的解释和实现。

那就是说,只有你的一个场所困扰我(强调我的):

  

ES要求我为改变状态和改变的一切事件发射事件   我的聚合根必须自己处理事件

通常AR会发出事件 - 他们不会处理它们。

在任何情况下,CQRS和ES都可以实现为完全持久性不可知(通常是)。事件存储为流,可以存储在关系数据库,NoSQL数据库,文件系统,内存等中。事件的存储通常在应用程序的边界实现(我将其视为基础结构) ),域模型不知道它们的流如何存储。

类似地,读取模型可以存储在任何可以想象的存储介质中。您可以拥有10种不同的读取模型和投影,每种模型和投影都存储在不同的数据库和不同的格式中。预测只是处理/读取事件流,否则完全与域分离。

它不再具有持久性不可知性。

答案 2 :(得分:0)

不确定这是多么正统,但是当前事件来源实体模型我做了类似的事情,这可能说明了不同之处。 。 。 (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) . . . . 

答案 3 :(得分:0)

我认为您仍然可以通过使用卫星POCO将域与持久性机制分离。然后,您可以围绕该POCO实施特定的持久性机制,并让您的域将其用作快照/ memento /状态。