CQRS、事件溯源和 Web 应用程序
CQRS, Event-Sourcing and Web-Applications
在阅读一些 CQRS 资源时,有一个反复出现的问题我没有听清。例如,假设客户端发出命令。此命令由域集成,因此它可以刷新其域模型(DM)。另一方面,命令保存在事件存储中。这是最常见的情况。
1) 当我们说 DM 被刷新时,我想数据是持久保存在底层数据库中的(如果有的话)。我对吗 ?否则,我们将处理内存瞬态模型,我想这不是一件好事? (状态不应该在客户端请求之外保留在服务器端的内存中)。
2) 如果数据持久化,我想依赖它的读取模型会自动更新,因为每个请求它的客户端都会在应用程序中生成一个新的 "state/context"(如果是 Web-应用程序或 RESTful 架构)?
3) 如果命令被持久化,这是否意味着我们处理事件源(当我们使用 CQRS 时通过构造)?事件溯源是否会使数据库更新过程无效? (好像状态是从事件存储中重建的,维护数据库似乎没用)?
CQRS 是否仅适用于多数据库系统(当数据在单独的数据库上传播时),并且,如果它处理内存瞬态模型,它是否适合 Web 应用程序或 RESTful 服务 ?
需要注意的是,唯一存储的是事件*。域模型是根据事件重建的。
所以是的,正如您所说,域模型是内存瞬态的,因为没有存储域模型的表示*仅存储发生在域中以使模型处于当前状态的事件。
当加载域模型中的元素时,会创建该元素的新实例,然后影响该实例的事件将以正确的顺序一个接一个地重播,以将元素放入正确的位置状态。
你可以保留域对象的实例并订阅新事件,这样它们就可以保持最新,而不必每次都从所有事件中加载它们,但通常它足以快速加载所有事件数据库并每次都以与每次调用 Web 服务时从数据库加载实例相同的方式应用它们。
*除非您有领域对象的快照以减少您需要 load/process
的事件数量
并非严格需要数据持久化。在足够多的不同位置 (GigaSpaces) 拥有足够的副本可能就足够了。所以不,不需要数据库。这是(至少在几年前)被荷兰 eBay 等价物用于生产。
1) 如前所述,唯一真正存储的是事件。
命令所做的唯一事情是在事件发生之前进行一致性检查。在伪代码中:
public void BorrowBook(BorrowableBook dto){
if (dto is valid)
RaiseEvent(new BookBorrowedEvent(dto))
else
throw exception
}
public void Apply(BookBorrowedEvent evt) {
this.aProperty = evt.aProperty;
...
}
当前状态通过顺序应用检索。因此,你必须在设计阶段特别注意,因为有一些常见的陷阱需要避免(也许你已经读过,但让我推荐一下 Martin Fowler 的 this 文章)。
到目前为止一切顺利,但这只是事件溯源。如果您决定使用不同的数据库来保存聚合状态,CQRS 就会发挥作用。
在我的项目中,我们有一个 projection,每 x 分钟在聚合上应用新事件(来自事件存储),并将结果保存在 MongoDB(表示层)的单独实例中将访问此数据库进行读取)。这个模型显然最终是一致的,但是这样你就真正把命令(写)和查询(读)分开了。
2) 如果您决定将写入模型与读取模型分开,您可以使用多种选项使它们同步:
- 每
x
秒从最后一个检查点应用事件(一些解决方案提供 快照 以避免重新应用繁重的命令)
- 订阅事件并在事件发生后立即更新读取模型的投影
3) 唯一存储的是事件。事实上,我们有一个事件存储,而不是命令存储:)
数据库没用了吗?要看!聚合到当前状态需要重新申请多少个事件?
三?也许你不需要为 read-model
建立数据库
在阅读一些 CQRS 资源时,有一个反复出现的问题我没有听清。例如,假设客户端发出命令。此命令由域集成,因此它可以刷新其域模型(DM)。另一方面,命令保存在事件存储中。这是最常见的情况。
1) 当我们说 DM 被刷新时,我想数据是持久保存在底层数据库中的(如果有的话)。我对吗 ?否则,我们将处理内存瞬态模型,我想这不是一件好事? (状态不应该在客户端请求之外保留在服务器端的内存中)。
2) 如果数据持久化,我想依赖它的读取模型会自动更新,因为每个请求它的客户端都会在应用程序中生成一个新的 "state/context"(如果是 Web-应用程序或 RESTful 架构)?
3) 如果命令被持久化,这是否意味着我们处理事件源(当我们使用 CQRS 时通过构造)?事件溯源是否会使数据库更新过程无效? (好像状态是从事件存储中重建的,维护数据库似乎没用)?
CQRS 是否仅适用于多数据库系统(当数据在单独的数据库上传播时),并且,如果它处理内存瞬态模型,它是否适合 Web 应用程序或 RESTful 服务 ?
需要注意的是,唯一存储的是事件*。域模型是根据事件重建的。
所以是的,正如您所说,域模型是内存瞬态的,因为没有存储域模型的表示*仅存储发生在域中以使模型处于当前状态的事件。
当加载域模型中的元素时,会创建该元素的新实例,然后影响该实例的事件将以正确的顺序一个接一个地重播,以将元素放入正确的位置状态。
你可以保留域对象的实例并订阅新事件,这样它们就可以保持最新,而不必每次都从所有事件中加载它们,但通常它足以快速加载所有事件数据库并每次都以与每次调用 Web 服务时从数据库加载实例相同的方式应用它们。
*除非您有领域对象的快照以减少您需要 load/process
的事件数量并非严格需要数据持久化。在足够多的不同位置 (GigaSpaces) 拥有足够的副本可能就足够了。所以不,不需要数据库。这是(至少在几年前)被荷兰 eBay 等价物用于生产。
1) 如前所述,唯一真正存储的是事件。 命令所做的唯一事情是在事件发生之前进行一致性检查。在伪代码中:
public void BorrowBook(BorrowableBook dto){
if (dto is valid)
RaiseEvent(new BookBorrowedEvent(dto))
else
throw exception
}
public void Apply(BookBorrowedEvent evt) {
this.aProperty = evt.aProperty;
...
}
当前状态通过顺序应用检索。因此,你必须在设计阶段特别注意,因为有一些常见的陷阱需要避免(也许你已经读过,但让我推荐一下 Martin Fowler 的 this 文章)。
到目前为止一切顺利,但这只是事件溯源。如果您决定使用不同的数据库来保存聚合状态,CQRS 就会发挥作用。 在我的项目中,我们有一个 projection,每 x 分钟在聚合上应用新事件(来自事件存储),并将结果保存在 MongoDB(表示层)的单独实例中将访问此数据库进行读取)。这个模型显然最终是一致的,但是这样你就真正把命令(写)和查询(读)分开了。
2) 如果您决定将写入模型与读取模型分开,您可以使用多种选项使它们同步:
- 每
x
秒从最后一个检查点应用事件(一些解决方案提供 快照 以避免重新应用繁重的命令) - 订阅事件并在事件发生后立即更新读取模型的投影
3) 唯一存储的是事件。事实上,我们有一个事件存储,而不是命令存储:)
数据库没用了吗?要看!聚合到当前状态需要重新申请多少个事件? 三?也许你不需要为 read-model
建立数据库