Events CQR中命令处理程序、聚合、存储库和事件存储之间的关系

Events CQR中命令处理程序、聚合、存储库和事件存储之间的关系,events,domain-driven-design,cqrs,Events,Domain Driven Design,Cqrs,我想了解基于CQRS的系统中命令处理程序、聚合、存储库和事件存储之间关系的一些细节 到目前为止,我所了解的是: 命令处理程序从总线接收命令。他们负责从存储库加载适当的聚合,并调用聚合上的域逻辑。一旦完成,它们将从总线中删除该命令 聚合提供行为和内部状态。国家从不公开。更改状态的唯一方法是使用行为。为该行为建模的方法从命令的属性创建事件,并将这些事件应用于聚合,聚合反过来调用相应设置内部状态的事件处理程序 存储库只允许在给定ID上加载聚合,并添加新聚合。基本上,存储库将域连接到事件存储 最后但并

我想了解基于CQRS的系统中命令处理程序、聚合、存储库和事件存储之间关系的一些细节

到目前为止,我所了解的是:

  • 命令处理程序从总线接收命令。他们负责从存储库加载适当的聚合,并调用聚合上的域逻辑。一旦完成,它们将从总线中删除该命令
  • 聚合提供行为和内部状态。国家从不公开。更改状态的唯一方法是使用行为。为该行为建模的方法从命令的属性创建事件,并将这些事件应用于聚合,聚合反过来调用相应设置内部状态的事件处理程序
  • 存储库只允许在给定ID上加载聚合,并添加新聚合。基本上,存储库将域连接到事件存储
  • 最后但并非最不重要的一点是,事件存储负责将事件存储到数据库(或使用的任何存储),并将这些事件作为所谓的事件流重新加载
到目前为止,一切顺利。 现在,我还没有了解到一些问题:

  • 如果一个命令处理程序要在一个已经存在的聚合上调用行为,那么一切都非常简单。命令处理程序获取对存储库的引用,调用其loadById方法,并返回聚合。但是,如果还没有聚合,但应该创建一个聚合,那么命令处理程序会做什么呢?据我所知,稍后应该使用事件重建聚合。这意味着创建聚合是为了响应fooCreated事件。但是为了能够存储任何事件(包括fooCreated事件),我需要一个聚合。因此,在我看来,这就像一个鸡和蛋的问题:我无法创建没有事件的聚合,但应该创建事件的唯一组件是聚合。所以基本上可以归结为:我如何创建新的聚合,谁做什么
  • 当聚合触发事件时,内部事件处理程序会响应它(通常通过apply方法调用),并更改聚合的状态。如何将此事件移交给存储库?谁发起了“请将新事件发送到存储库/事件存储”操作?总量本身?通过查看聚合来访问存储库?订阅内部活动的其他人
  • 最后但并非最不重要的一点是,我在正确理解事件流的概念时遇到了一个问题:在我的想象中,它只是类似于事件的有序列表。重要的是它是“有序的”。是这样吗

以下内容基于我自己的经验以及我对Lokad.CQRS、NCQRS等各种框架的实验。我相信有多种方法可以处理这一问题。我将发布对我最有意义的内容

1。聚合创建:

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(或任何其他机制对此进行再水化)并不冲突。有时工厂只是一个静态方法(适合于“上下文”/“意图”捕获),有时它是另一个聚合的实例方法(适合于“数据”继承),有时它是一个显式工厂对象(适合于“复杂”创建逻辑)
  • 我想提供一个解释