DDD & CQRS & Event Sourcing

一、 DDD分层架构

Evans在它的《领域驱动设计:软件核心复杂性应对之道》书中推荐采用分层架构去实现领域驱动设计:


DDD是近年软件设计的热门。CQRS与Event Sourcing作为实施DDD的一种选择,也逐步进入人们的视野。围绕这两个主题,软件开发的大咖[Martin Fowler]、[Greg Young]、[Udi Dahan]分别有所论述,[MSDN CQRS Journey]、[Implementing DDD]、[Patterns, Principles, and Practices of DDD]等著述也提供了范例,国内外各大论坛的文章和DDD开源框架更是数不胜数,为学习CQRS和Event Sourcing提供了大量指导。

其中,Greg Young的论文最为系统。故本文参照其论文的结构,简单梳理了CQRS与Event Sourcing的发展脉络,厘出其中的主要技术重点进行解读,并提出以Akka作为落地方案,以求对这两个主题有一个较为全面的总结。错谬之处,还望指正。

二、CQRS架构

命令查询的责任分离Command Query Responsibility Segregation (简称CQRS)模式是一种架构体系模式,能够使改变模型的状态的命令和模型状态的查询实现分离。这属于DDD应用领域的一个模式,主要解决DDD在数据库报表输出上处理方式。

Greg Young在infoQ的采访中“State Transitions in Domain-Driven Design”谈到了CQRS,Greg 解释了把领域模型分为两种:状态校验,以及状态转换,维持当前状态的一个视图。


在客户端就将数据的CRUD的新增修改删除CUD等操作和查询R进行分离,前者称为Command,走Command bus进入Domain对模型进行操作,而查询则从另外一条路径直接使用SQL对数据进行操作,比如报表输出等,发挥SQL的特点。

当一个Command进来时,从仓储Repository加载一个聚合aggregate对象群,然后执行其方法和行为。这样,会激发聚合对象群产生一个事件,这个事件可以分发给仓储Repository,或者分发给Event Bus事件总线,比如JavaEE的消息总线等等。事件总线将再次激活所有监听本事件的处理者。当然一些处理者会执行其他聚合对象群的操作,包括数据库的更新。

三、事件溯源(Event Sourcing)

Event Sourcing

一个对象从创建开始到消亡会经历很多事件,以前我们是在每次对象参与完一个业务动作后把对象的最新状态持久化保存到数据库中,也就是说我们的数据库中的数据是反映了对象的当前最新的状态。而事件溯源则相反,不是保存对象的最新状态,而是保存这个对象所经历的每个事件,所有的由对象产生的事件会按照时间先后顺序有序的存放在数据库中。可以看出,事件溯源的这种做法是更符合事实观的,因为它完整的描述了对象的整个生命周期过程中所经历的所有事件。

那么,事件到底如何影响一个领域对象的状态的呢?很简单,当我们在触发某个领域对象的某个行为时,该领域对象会先产生一个事件,然后该对象自己响应该事件并更新其自己的状态,同时我们还会持久化在该对象上所发生的每一个事件;这样当我们要重新得到该对象的最新状态时,只要先创建一个空的对象,然后将和该对象相关的所有事件按照事件发生先后顺序从先到后再全部应用一遍即可还原得到该对象的最新状态,这个过程就是所谓的事件溯源;

另一方面,因为是用事件来表示对象的状态,而事件是只会增加不会修改。这就能让数据库里的表示对象的数据非常稳定,不可能存在DELETE或UPDATE等操作。因为一个事件就是表示一个事实,事实是不能被磨灭或修改的。这种特性可以让领域模型非常稳定,在数据库级别不会产生并发更新同一条数据的问题;其实CAP定理之所以做不到,根本原因也是由于数据可以被修改;现在通过事件溯源,我们实现CAP或许就成为了可能;

我们可以看到,基于这样的设计,领域对象的状态完全是由事件驱动的。不仅如此,事件还可以被事件总线分发出去,通知领域模型外的一切事件响应者发生了什么,基于这种Publish-Subscribe的通信模式,我们可以最大限度的实现系统的松耦合。

四、使用Akka框架实现

Actor模型最早出自1973年Carl Hewitt等人所著论文A Universal Modular ACTOR Formalism for Artificial Intelligence。

Akka是Lightbend公司推出的一个基于Actor模型的分布式框架,目前主要支持的语言包括Java和Scala。
以下是官网及我的笔记链接:

  • [Akka Typed,最新版本2.6.10]
  • [一图看懂Actor Typed]
  • [Akka Typed 官方文档之随手记]
    实现细节
    用Akka实现CQRS与Event Sourcing的示意图如下:
  • 由Command与Event组成的Protocol,是Actor与外界沟通的唯一媒介。
  • EventSourcedBehavior是Write Model的核心,承担着聚合的主体责任,主要定义了Command和Event的Handler。
  • Event的SequenceNumber由框架自动生成,reply和snapshot由框架提供。
  • 聚合状态单独定义在State里,借State模式实现状态迁移。
  • Tag为事件做上标记,方便Read Model选择使用。
  • PersistenceQuerier是Read Model的核心,负责从Read Journal中根据Tag读取事件流,更新自身的读数据模型,从而实现读写模型的最终一致性。
  • Serialize为Command和Event提供序列化支持,可使用Json或二进制格式。

Akka的CQRS示例,分别有Scala和Java版本
微服务
Lightbend公司在Akka基础上,推出了一个微服务框架Lagom。

Lagom框架坚持,微服务是按服务边界Boundary将系统切分为若干个组成部分的结果,这意味着要使它们与限界上下文Bounded Context、业务功能和模块隔离等要求保持一致,才能达到可伸缩性和弹性要求,从而易于部署和管理。因此,在设计微服务时应考虑大小是否“Lagom”,而非是否足够“Micro”。
以下是官网和我的笔记链接:

  • [Lagom,最新版本1.6.4]
  • [Lagom 官方文档之随手记]
    Lagom封装了服务定位、服务网关、消息队列和路由、集群等功能。每个服务由服务描述子、调用标识符、消息处理器等组成,在服务的内部实现中,由Akka提供的EventSourcedBehavior承担实际的消息处理和持久化。

Lagom的Hello World示例,分别有Scala和Java版本

你可能感兴趣的:(DDD & CQRS & Event Sourcing)