[英]Relation between command handlers, aggregates, the repository and the event store in CQRS
我想了解基于 CQRS 的系统中命令处理程序、聚合、存储库和事件存储之间关系的一些细节。
到目前为止我所理解的:
到现在为止还挺好。 现在有一些我还没有得到的问题:
以下内容基于我自己的经验以及我对 Lokad.CQRS、NCQRS 等各种框架的实验。我相信有多种方法可以解决这个问题。 我会发布对我来说最有意义的内容。
1. 聚合创建:
每次命令处理程序需要聚合时,它都会使用存储库。 存储库从事件存储中检索相应的事件列表并调用重载的构造函数,注入事件
var stream = eventStore.LoadStream(id)
var User = new User(stream)
如果聚合之前不存在,则流将为空,新创建的对象将处于其原始状态。 您可能希望确保在此状态下只允许使用少数命令使聚合User.Create()
,例如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)
更新:
作为旁注:Yves 的回答让我想起了Udi Dahan几年前的一篇有趣的文章:
丹尼斯优秀答案的一个小变化:
我几乎同意 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 应用程序投射的伪代码; 真正的代码应该注入一些东西,并在其他类中重构其他职责。 想法是保持聚合尽可能干净并避免代码重复。
关于聚合的一些重要方面:
可以在此处找到一个开源 PHP 实现。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.