命令和查询责任分离(CQRS)是由Greg Young提出的一种将系统的读(查询)、写(命令)操作分离为两种独立子系统的架构模式。命令通常是异步执行的,并存储在一个事务型数据库中,而读操作则通常是最终一致的,并且数据来自于解正规化的视图。
本文在此提出并为读者展示一种为CQRS系统创建一套RESTful API的方式。这种方式结合了HTTP的语义、REST API基于资源的风格,并能够处理分布式计算的某些问题,例如最终一致性和并发性。
此外我们还提供了一套原型API,它建立于Greg Young编写的m-r CQRS原型之上,后者也被称为SimplestPossibleThing。m-r可以认为是CQRS原型的事实标准,它鼓舞了许多团队采用并创建CQRS系统。虽然这个m-r原型很简单,但它已经能够展示在现实世界中使用RESTful CQRS系统的某些机遇和挑战了。
我们在将下一部分审阅m-r的领域模型,随后对相关特性的API设计进行一些探索。最后,我们将对一些所做的选择展开讨论,并且讨论一些RESTful m-r的概念和理论内容。
m-r领域
m-r模型是一个经过简化的库存管理系统的领域模型,你可以创建新库存物品(假设它是某种类型的产品),重命名或取消激活(即逻辑删除)它们。被取消激活的物品将不再为用户所见,而所有活动的物品都可以被获取,并且能够看到每个物品的所有细节。你也能够增加或减少这些库存物品,指定所加入或减少的物品数据。换句话说,在建立库存量之后,就可以开始使用这个系统了。
用户将通过同步的查询来查看物品列表或是物品细节,对于物品状态的修改将通过命令来实现。在现实世界中,命令应该是异步执行的,但由于代码中使用了内存中的事件总线(Event Bus)及事件处理函数,因此在最终实现中命令都是同步执行的。
m-r模型实现了CQRS:命令和查询被分别存储在不同的地方,并且各自由系统中完全不同的部分进行处理。
除了CQRS之外,m-r也使用了事件溯源(Event Sourcing)作为它的持久化机制。在这种方式中,对于领域模型的修改会被捕获为一系列的事件,这些事件会按照它们被调用的顺序存储起来。为了获取某个模型的当前状态,需要将所有事件按照它们发生的顺序进行重播。换句话说,模型中实体的状态信息是不会被持久化的。举例来说,如果我们创建了一个库存物品,随后将它重命名两次,那么我们将会得到一个InventoryItemCreated事件和两个InventoryItemRenamed事件,这些事件都会被保存在事件存储(Event Store)中。
事件是连续的,并且每个事件都带有一个版本号,用以在并发时进行检查。举例来说,如果某个库存物品在版本2的基础上进行重命名,但正好有另一个重命名发生在同一个物品上,并使它的当前版本变为3,那么这种情况就会导致并发异常。
命令与领域事件通常是一对一的关系,当调用了某个命令之后,领域模型会发起并存储一个事件。领域事件是事件溯源的基石,它和跨多个边界上下文(bounded context)的事件不同,往往粒度更细,并且只包括所需的最小数量的信息。因此,它并不是一个适合于在不同的边界上下文之间进行集成的工具。除了使用一个进程内的事件总线之外,m-r还用到了一个内存中的事件存储。这个存储本质就是一个哈希表,它使用模型的id作为键,并且持续跟踪模型中发生的任何事件。
如欲了解CQRS和事件溯源的更多信息,你可以阅读Greg Young的这本迷你书。