Events CQR中命令处理程序、聚合、存储库和事件存储之间的关系
我想了解基于CQRS的系统中命令处理程序、聚合、存储库和事件存储之间关系的一些细节 到目前为止,我所了解的是:Events CQR中命令处理程序、聚合、存储库和事件存储之间的关系,events,domain-driven-design,cqrs,Events,Domain Driven Design,Cqrs,我想了解基于CQRS的系统中命令处理程序、聚合、存储库和事件存储之间关系的一些细节 到目前为止,我所了解的是: 命令处理程序从总线接收命令。他们负责从存储库加载适当的聚合,并调用聚合上的域逻辑。一旦完成,它们将从总线中删除该命令 聚合提供行为和内部状态。国家从不公开。更改状态的唯一方法是使用行为。为该行为建模的方法从命令的属性创建事件,并将这些事件应用于聚合,聚合反过来调用相应设置内部状态的事件处理程序 存储库只允许在给定ID上加载聚合,并添加新聚合。基本上,存储库将域连接到事件存储 最后但并
- 命令处理程序从总线接收命令。他们负责从存储库加载适当的聚合,并调用聚合上的域逻辑。一旦完成,它们将从总线中删除该命令
- 聚合提供行为和内部状态。国家从不公开。更改状态的唯一方法是使用行为。为该行为建模的方法从命令的属性创建事件,并将这些事件应用于聚合,聚合反过来调用相应设置内部状态的事件处理程序
- 存储库只允许在给定ID上加载聚合,并添加新聚合。基本上,存储库将域连接到事件存储
- 最后但并非最不重要的一点是,事件存储负责将事件存储到数据库(或使用的任何存储),并将这些事件作为所谓的事件流重新加载
- 如果一个命令处理程序要在一个已经存在的聚合上调用行为,那么一切都非常简单。命令处理程序获取对存储库的引用,调用其loadById方法,并返回聚合。但是,如果还没有聚合,但应该创建一个聚合,那么命令处理程序会做什么呢?据我所知,稍后应该使用事件重建聚合。这意味着创建聚合是为了响应fooCreated事件。但是为了能够存储任何事件(包括fooCreated事件),我需要一个聚合。因此,在我看来,这就像一个鸡和蛋的问题:我无法创建没有事件的聚合,但应该创建事件的唯一组件是聚合。所以基本上可以归结为:我如何创建新的聚合,谁做什么
- 当聚合触发事件时,内部事件处理程序会响应它(通常通过apply方法调用),并更改聚合的状态。如何将此事件移交给存储库?谁发起了“请将新事件发送到存储库/事件存储”操作?总量本身?通过查看聚合来访问存储库?订阅内部活动的其他人李>
- 最后但并非最不重要的一点是,我在正确理解事件流的概念时遇到了一个问题:在我的想象中,它只是类似于事件的有序列表。重要的是它是“有序的”。是这样吗
UserCommandHandler
Handle(CreateUser cmd)
stream = store.LoadStream(cmd.UserId)
user = new User(stream.Events)
user.Create(cmd.UserName, ...)
store.AppendToStream(cmd.UserId, stream.Version, user.Changes)
Handle(BlockUser cmd)
stream = store.LoadStream(cmd.UserId)
user = new User(stream.Events)
user.Block(string reason)
store.AppendToStream(cmd.UserId, stream.Version, user.Changes)
User
created = false
blocked = false
Changes = new List<Event>
ctor(eventStream)
isNewEvent = false
foreach (event in eventStream)
this.Apply(event, isNewEvent)
Create(userName, ...)
if (this.created) throw "User already exists"
isNewEvent = true
this.Apply(new UserCreated(...), isNewEvent)
Block(reason)
if (!this.created) throw "No such user"
if (this.blocked) throw "User is already blocked"
isNewEvent = true
this.Apply(new UserBlocked(...), isNewEvent)
Apply(userCreatedEvent, isNewEvent)
this.created = true
if (isNewEvent) this.Changes.Add(userCreatedEvent)
Apply(userBlockedEvent, isNewEvent)
this.blocked = true
if (isNewEvent) this.Changes.Add(userBlockedEvent)
每当命令处理程序需要聚合时,它都会使用存储库。存储库从事件存储中检索相应的事件列表,并调用重载构造函数,注入事件
var stream = eventStore.LoadStream(id)
var User = new User(stream)
如果聚合之前不存在,则流将为空,新创建的对象将处于其原始状态。您可能希望确保在此状态下,只允许使用少数命令来激活聚合,例如User.Create()
2。存储新事件
命令处理发生在工作单元内部。在命令执行期间,每个结果事件都将添加到聚合中的列表中(User.Changes
)。执行完成后,更改将附加到事件存储中。在下面的示例中,这发生在以下行中:
store.AppendToStream(cmd.UserId, stream.Version, user.Changes)
3。事件顺序
想象一下,如果两个随后的CustomerMoved
事件以错误的顺序重播,会发生什么
示例
我将尝试用一段伪代码来说明这一点(我故意将存储库问题留在命令处理程序中,以显示幕后会发生什么):
应用程序服务:
UserCommandHandler
Handle(CreateUser cmd)
stream = store.LoadStream(cmd.UserId)
user = new User(stream.Events)
user.Create(cmd.UserName, ...)
store.AppendToStream(cmd.UserId, stream.Version, user.Changes)
Handle(BlockUser cmd)
stream = store.LoadStream(cmd.UserId)
user = new User(stream.Events)
user.Block(string reason)
store.AppendToStream(cmd.UserId, stream.Version, user.Changes)
User
created = false
blocked = false
Changes = new List<Event>
ctor(eventStream)
isNewEvent = false
foreach (event in eventStream)
this.Apply(event, isNewEvent)
Create(userName, ...)
if (this.created) throw "User already exists"
isNewEvent = true
this.Apply(new UserCreated(...), isNewEvent)
Block(reason)
if (!this.created) throw "No such user"
if (this.blocked) throw "User is already blocked"
isNewEvent = true
this.Apply(new UserBlocked(...), isNewEvent)
Apply(userCreatedEvent, isNewEvent)
this.created = true
if (isNewEvent) this.Changes.Add(userCreatedEvent)
Apply(userBlockedEvent, isNewEvent)
this.blocked = true
if (isNewEvent) this.Changes.Add(userBlockedEvent)
聚合:
UserCommandHandler
Handle(CreateUser cmd)
stream = store.LoadStream(cmd.UserId)
user = new User(stream.Events)
user.Create(cmd.UserName, ...)
store.AppendToStream(cmd.UserId, stream.Version, user.Changes)
Handle(BlockUser cmd)
stream = store.LoadStream(cmd.UserId)
user = new User(stream.Events)
user.Block(string reason)
store.AppendToStream(cmd.UserId, stream.Version, user.Changes)
User
created = false
blocked = false
Changes = new List<Event>
ctor(eventStream)
isNewEvent = false
foreach (event in eventStream)
this.Apply(event, isNewEvent)
Create(userName, ...)
if (this.created) throw "User already exists"
isNewEvent = true
this.Apply(new UserCreated(...), isNewEvent)
Block(reason)
if (!this.created) throw "No such user"
if (this.blocked) throw "User is already blocked"
isNewEvent = true
this.Apply(new UserBlocked(...), isNewEvent)
Apply(userCreatedEvent, isNewEvent)
this.created = true
if (isNewEvent) this.Changes.Add(userCreatedEvent)
Apply(userBlockedEvent, isNewEvent)
this.blocked = true
if (isNewEvent) this.Changes.Add(userBlockedEvent)
用户
已创建=错误
阻塞=错误
更改=新列表
执行主任(事件流)
isNewEvent=false
foreach(事件流中的事件)
this.Apply(事件,isNewEvent)
创建(用户名,…)
如果(this.created)抛出“用户已存在”
isNewEvent=true
this.Apply(新用户创建(…),isNewEvent)
阻塞(原因)
如果(!this.created)抛出“没有这样的用户”
如果(this.blocked)抛出“用户已被阻止”
isNewEvent=true
this.Apply(newuserblocked(…),isNewEvent)
应用(userCreatedEvent、isNewEvent)
this.created=true
如果(isNewEvent)this.Changes.Add(userCreatedEvent)
应用(userBlockedEvent、isNewEvent)
this.blocked=true
如果(isNewEvent)this.Changes.Add(userBlockedEvent)
更新:
UserCommandHandler
Handle(CreateUser cmd)
stream = store.LoadStream(cmd.UserId)
user = new User(stream.Events)
user.Create(cmd.UserName, ...)
store.AppendToStream(cmd.UserId, stream.Version, user.Changes)
Handle(BlockUser cmd)
stream = store.LoadStream(cmd.UserId)
user = new User(stream.Events)
user.Block(string reason)
store.AppendToStream(cmd.UserId, stream.Version, user.Changes)
User
created = false
blocked = false
Changes = new List<Event>
ctor(eventStream)
isNewEvent = false
foreach (event in eventStream)
this.Apply(event, isNewEvent)
Create(userName, ...)
if (this.created) throw "User already exists"
isNewEvent = true
this.Apply(new UserCreated(...), isNewEvent)
Block(reason)
if (!this.created) throw "No such user"
if (this.blocked) throw "User is already blocked"
isNewEvent = true
this.Apply(new UserBlocked(...), isNewEvent)
Apply(userCreatedEvent, isNewEvent)
this.created = true
if (isNewEvent) this.Changes.Add(userCreatedEvent)
Apply(userBlockedEvent, isNewEvent)
this.blocked = true
if (isNewEvent) this.Changes.Add(userBlockedEvent)
作为旁注:Yves的回答让我想起了Udi Dahan几年前写的一篇有趣的文章:
- 当处理“创造性”用例(即,应该衍生出新的聚合)时,尝试寻找另一个聚合或工厂,您可以将该责任转移到该聚合或工厂。这与有一个将事件转化为水合物的CTR(或任何其他机制对此进行再水化)并不冲突。有时工厂只是一个静态方法(适合于“上下文”/“意图”捕获),有时它是另一个聚合的实例方法(适合于“数据”继承),有时它是一个显式工厂对象(适合于“复杂”创建逻辑)
- 我想提供一个解释