我想了解基于CQRS的系统中命令处理程序,聚合,存储库和事件存储之间关系的一些细节。
到目前为止我所理解的是:
到目前为止,这么好。 现在有一些我还没有得到的问题:
答案 0 :(得分:33)
以下是基于我自己的经验以及我对各种框架(如Lokad.CQRS,NCQRS等)的实验。我确信有多种方法可以解决这个问题。我会发布对我来说最有意义的内容。
<强> 1。聚合创建:
每次命令处理程序需要聚合时,它都会使用存储库。存储库从事件存储中检索相应的事件列表,并调用重载的构造函数,注入事件
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)
foreach (event in eventStream)
this.Apply(event)
Create(userName, ...)
if (this.created) throw "User already exists"
this.Apply(new UserCreated(...))
Block(reason)
if (!this.created) throw "No such user"
if (this.blocked) throw "User is already blocked"
this.Apply(new UserBlocked(...))
Apply(userCreatedEvent)
this.created = true
this.Changes.Add(userCreatedEvent)
Apply(userBlockedEvent)
this.blocked = true
this.Changes.Add(userBlockedEvent)
<强>更新强>
作为旁注:Yves的回答让我想起了几年前 Udi Dahan 的一篇有趣的文章:
答案 1 :(得分:11)
Dennis的一个小变化很好的答案:
答案 2 :(得分:0)
我几乎同意yves-reynhout和dennis-traub,但我想告诉你我是怎么做到的。我想剥夺我的责任,将事件应用于自己或重新补充水分;否则会有很多代码重复:每个聚合构造函数看起来都一样:
UserAggregate:
ctor(eventStream)
foreach (event in eventStream)
this.Apply(event)
OrderAggregate:
ctor(eventStream)
foreach (event in eventStream)
this.Apply(event)
ProfileAggregate:
ctor(eventStream)
foreach (event in eventStream)
this.Apply(event)
这些责任可以留给命令调度员。该命令由聚合直接处理。
Command dispatcher class
dispatchCommand(command) method:
newEvents = ConcurentProofFunctionCaller.executeFunctionUntilSucceeds(tryToDispatchCommand)
EventDispatcher.dispatchEvents(newEvents)
tryToDispatchCommand(command) method:
aggregateClass = CommandSubscriber.getAggregateClassForCommand(command)
aggregate = AggregateRepository.loadAggregate(aggregateClass, command.getAggregateId())
newEvents = CommandApplier.applyCommandOnAggregate(aggregate, command)
AggregateRepository.saveAggregate(command.getAggregateId(), aggregate, newEvents)
ConcurentProofFunctionCaller class
executeFunctionUntilSucceeds(pureFunction) method:
do this n times
try
call result=pureFunction()
return result
catch(ConcurentWriteException)
continue
throw TooManyRetries
AggregateRepository class
loadAggregate(aggregateClass, aggregateId) method:
aggregate = new aggregateClass
priorEvents = EventStore.loadEvents()
this.applyEventsOnAggregate(aggregate, priorEvents)
saveAggregate(aggregateId, aggregate, newEvents)
this.applyEventsOnAggregate(aggregate, newEvents)
EventStore.saveEventsForAggregate(aggregateId, newEvents, priorEvents.version)
SomeAggregate class
handleCommand1(command1) method:
return new SomeEvent or throw someException BUT don't change state!
applySomeEvent(SomeEvent) method:
changeStateSomehow() and not throw any exception and don't return anything!
请记住,这是从PHP应用程序投射的伪代码;真正的代码应该注入事物,并在其他类中重构其他职责。这意味着尽可能保持聚合清晰,避免代码重复。
关于聚合的一些重要方面:
可以找到here的开源PHP实现。