1、前言

领域驱动(Domain – Driven Design)设计的理念在于建立一系列既符合软件所处领域本身又适合软件分析开发需要的领域模型。命令查询与职责分离(Command Query Responsibility Segregation)和事件溯源(E、vent Sourcing)是为一种领域驱动设计的实践。

本文旨在简要介绍CQRS & Event Sourcing, 希望能够给大家在设计业务系统上提供一种新的思路和选择。

2、领域驱动设计

在开始介绍CQRS之前,有必要先了解DDD中的一些基本思想和概念。

各行各业都有业务系统和软件开发的需求。比如Fintech公司会开发贷款业务系统,证券公司会开发股票行情交易软件,旅行社会开发在线旅游网站。虽然作为程序员,我赞成大家都能博学多才,上晓天文,下知地理。但是毕竟术业有专攻,做贷款业务系统时,我们需要请教信贷专家;研发股票行情交易系统时,我们会和交易员一起讨论;实践旅游网站时,兴许我们需要请教资深的旅行家……

那么当我们和领域专家围坐一起,高谈阔论之时,我们怎样才能做到有效的沟通,而不是鸡同鸭讲呢?DDD给了我们如下一些启发:

1、确定的领域模型(Domain Model)

明确的领域模型是一切的基础。

一个良好定义的领域模型一般会有以下几个特点:

该模型应该包含所有来自领域专家的知识

该模型可以让开发团队很清楚的界定领域边界,并且判断知识的上下文一致性

开发者可以将该模型以代码的形式进行表述

该模型可以方便地应对来自领域的变化

2、通用的语言(Ubiquitous Language)

对于领域中的名词,概念,所有的开发者和领域专家会采用同样的词汇,并且有着同样的理解。

比如在支付系统中,“渠道”可以是“微信支付”,“连连支付”,而不是“工商银行”, “招商银行”。如果开发者和领域专家的理解不同,那最后设计出来的产品势必南辕北辙。

3、实体(Entities),值(value), 服务(Services)

实体有一个全局唯一的标识,并且在整个生命周期中不变。

比如在支付系统中,一个交易(Transaction)就是一个实体。每个交易都有自己独有的交易ID。

值没有一个唯一的标识。比如在支付系统中。

交易的状态可以分为:“创建中”,“处理中”,“成功”,“失败”。 所以交易的状态就是一个值。

服务:除了实体和值之外,对于描述的动作,领域驱动的设计认为这是一个服务。

比如在支付系统中,与第三方支付绑定银行卡的行为,我们就可以认为是一种服务。

4、聚合(Aggregate)和聚合根(Aggregate Root)

相对于实体,值和服务是用来进行领域驱动设计中的建模模型。聚合和聚合根则是根据领域的原则分割并且描述实体之间的组合。

想象一下,如果一个系统中有许多用户,每个用户都可以修改部分数据。那么如何保证数据的一致性问题:

当每一个用户修改数据时,将数据库中所有的表都锁定。确实,这样可以保证强一致性,但是这肯定不是一个用户体验好的系统,并且性能十分糟糕

当每一个用户修改数据时,只将部分数据锁定。如此在用户的可用性和系统的一致性上能取到权衡

这里的问题就在于,如何界定B方案中的“部分”, DDD认为聚合就是在考量系统一致性后,相关的实体和值组合在一起的最小不可分割的集合。而聚合根本质仍然是一个实体, 在DDD中认为聚合根是访问聚合的唯一方式。

可能说的比较抽象,仍然以支付系统为例:我们认为“订单”就是一个聚合。订单可以包含多个“交易”, 同时订单也是一个实体,因为订单号是订单的唯一“标识”,订单本身也可以作为订单自身聚合的聚合根,外界通过访问订单才能访问订单中的交易。

以上便是DDD的一些基本概念,作为开发者而言,我并不赞同概念的堆砌和教条主义,其实很多时候,我们已经不自觉的使用了DDD的一些概念潜移默化的指导我们平时的软件开发,比如我们会在开发的小组内,使用约定俗称的名词,开发者和业务员都能明白这些没有歧义的名词(通用语言), 开发者也会站在业务员的角度思考软件系统内部设计分割的原则(聚合/聚合根的设计)。我相信DDD不是软件设计的条条框框,而是大量软件设计实践后,对于良好设计范式的一个总结和提炼。

3、命令查询与职责 & 事件溯源的系统架构

首先我们来看一个经典的基于数据驱动(Data Driven)的系统设计结构:

这是一个非常经典的系统设计,数据驱动的架构有很多现代ORM可以方便地实现基本的功能,优点不言而喻。我们就来谈谈这样的系统的局限性:

1、无法实践领域驱动编程

很明显,这样系统对领域对象最基本的操作就是增删改查(CURD),但是增删改查是计算机世界的术语,并不是一个领域的通用语言。在领域世界中,通用的语言远远比增删改查复杂的多。

还是以支付系统为例:比如创建“订单”,这并不是一个简单的增加操作。因为“订单”可能包含多个“交易”,所以创建“订单”其实包括增加一个“订单”信息以及增加该订单下的多个“交易”信息,并且最终将订单和交易增加(序列化)到数据存储中。而查询订单,则包括查询订单所属的“交易”,最终组合出订单并且返回。

如果最终的数据存储是一个关系型的数据库,则创建“订单”和查询“订单”的操作,需要开发人员理解订单和交易的关系,并且转换订单/交易模型至一个关系型的数据库。

2、单一的对象实体作用于数据读写

仍然以支付系统为例:无论是增加订单还是查询订单,在这样的系统中,订单被建模成一个单一的实体。无论是存储还是读取,都会将整个订单对象序列化到数据存储或者反序列出来。如果我们只是修改一个订单的状态,并且查询最新的订单状态,真的需要将整个对象都写入数据存储或者读取出来吗?撇开系统性能而言,安全性也是一个值得考量的问题。

基于对现实世界的观察,任何的方法,都可以拆解为两类:命令和查询。查询负责返回数据,并且不改变数据的状态。命令负责改变数据的状态,产生事件,但是不返回任何数据。任何复杂的方法(DDD中的服务),都可以是命令和查询的组合。

由此,我们来看一下基于CQRS & Event Sourcing的系统设计:

在CQRS的架构设计中,客户端可以发送命令,或者要求查询。对于命令而言,由命令总线负责分发给相应的命令处理器。命令处理器通过事件溯源加载得到相应的聚合根,修改聚合,并且产生相应的事件。事件首先会被存储,继而被事件总线分发给事件处理器。由事件处理器根据相应的事件将领域模型转换成写数据库中的存储表现形式。

写数据库可以以一种可靠的方式,将数据同步到读数据库,对于接受最终一致性的系统而言,这是可以接受的同步方式。

而另一方面,对应查询的需求,可以由简单的查询处理器接受查询请求,将写数据库中的数据转换成查询需要的形式予以返回。

事件溯源是一种通过采集所有的历史事件还原一个聚合状态的方法。

以一个支付系统的订单(聚合)为例。订单的生命周期可以是创建 -> 待计划 -> 执行中 -> 完成。那么对应的事件可以是订单创建事件,订单计划执行事件,订单执行事件,订单完成事件。

对于普通数据驱动的设计而言,订单的信息存储可能是这样的:(关系型数据库)

而对于支持事件溯源的系统而言,订单的存储可以是这样的:

{

“aggregateId” : “201609011005”

“eventPayload “: “Created”

“timeStamp” : “2016/09/01 10:50:01 ”

}

{

“aggregateId” : “201609011005”

“eventPayload “: “Scheduled”

“timeStamp” : “2016/09/01 10:51:11 ”

}

{

“aggregateId” : “201609011005”

“eventPayload “: “Executing”

“timeStamp” : “2016/09/01 18:02:59 ”

}

事件溯源将这三个事件依次加载处理,便可以还原出订单的现在状态。在聚合事件数量大的情况下,采用事件快照(Event Snapshot)可以有效提高事件溯源的效率和速度。

经过以上分析,在CQRS & Event Sourcing的设计中,我们可以看到以下优点:

读写分离:不同于数据驱动的设计,读写使用的同一个流程,甚至是同一个模型。在CQRS中,领域模型根据事件序列化至数据库。而查询模块则完全可以定义需要查询领域模型。读写是完全隔离的。如果使用数据库同步的方式,读写甚至可以使用不同的数据库(取决于系统对一致性的需求)。所以在这里,我们可以提高系统的吞吐量和性能。并且可以分别对写数据库和读数据库做出针对性的优化。

符合领域设计的原则:无论是命令还是事件,都是基于对现实世界的观察。不同于增删改,整个系统是由命令和事件驱动,由命令对相应的聚合(实体)进行修改。而修改则产生了相应的事件,事件可以再产生命令,如此往复。

我们的世界此刻不正是由无数个事件叠加产生的结果吗?

整个系统的所有事件都有历史记录:对于任何聚合的生命周期中,如何被创建,修改直至回收的过程,都可以通过一个又一个事件被回溯,分析。我们不仅仅关心聚合最终状态,对中间记录的分析同样也有价值。

同样,CQRS & Event Sourcing 也有自身的局限性:

系统结构相对于经典的设计而言复杂。需要设计命令总线,命令分发器,事件总线,事件分发器。需要设计良好的事件存储机制,以及事件溯源机制

对于简单,静态的系统,或者是没有复杂协作上下文(Bounded Context)的领域模型的系统,引入CQRS并不会得到很多益处,相反会使得系统臃肿,庞大

因为引入了部分DDD领域设计的概念,对于开发人员也有一定的学习曲线

4、总结

CQRS & ES 给我们提供了一种有别于传统经典体系的设计思路,在业务系统中分析哪里需要使用CQRS & ES 需要我们权衡实施这种新体系架构所需的代价和长期的回报。此文简要介绍了CQRS & ES 在领域驱动设计内的实践,希望能抛砖引玉,与诸君共勉。

文章来源:https://www.jianshu.com/p/9a3f8d514fcd

领域驱动设计的实践 – CQRS Event Sourcing相关推荐

  1. 领域驱动设计(DDD)实践之路(三):如何设计聚合

    本文首发于 vivo互联网技术 微信公众号  链接:https://mp.weixin.qq.com/s/oAD25H0UKH4zujxFDRXu9Q 作者:wenbo zhang [领域驱动设计实践 ...

  2. 领域驱动设计(DDD)实践之路(四):领域驱动在微服务设计中的应用

    这是"领域驱动设计实践之路"系列的第四篇文章,从单体架构的弊端引入微服务,结合领域驱动的概念介绍了如何做微服务划分.设计领域模型并展示了整体的微服务化的系统架构设计.结合分层架构. ...

  3. DDD 领域驱动设计落地实践:六步拆解 DDD

    引言 相信通过前面几篇文章的介绍,大家对于 DDD 的相关理论以及实践的套路有了一定的理解,但是理解 DDD 理论和实践手段是一回事,能不能把这些理论知识实际应用到我们实际工作中又是另外一回事,因此本 ...

  4. 【DDD落地实践系列】DDD 领域驱动设计落地实践:六步拆解 DDD

    引言 相信通过前面几篇文章的介绍,大家对于 DDD 的相关理论以及实践的套路有了一定的理解,但是理解 DDD 理论和实践手段是一回事,能不能把这些理论知识实际应用到我们实际工作中又是另外一回事,因此本 ...

  5. 【你问我答】DDD(领域驱动设计)实践中遇到问题了?尽管问,我们负责解答!...

    点击上方"蓝字"可以订阅哦 [你问我答]是由美团点评技术团队推出的线上问答服务,你在工作学习中遇到的各种技术问题,都可以通过我们微信公众号发问,我们6000+工程师会义务为你解答, ...

  6. 领域驱动设计理论实践

    战略设计 战略设计是将"混沌"解构成"清晰"的过程,在该过程从开始到结束的历程之中,我们会划分出领域.界定通用语言范围.确定出系统限界上下文以及上下文之间的映射 ...

  7. DDD 领域驱动设计落地实践系列:工程结构分层设计

    引言 前面几篇文章中,笔者给大家阐述了 DDD 领域驱动设计的三大过程,重点围绕如何通过战略设计与战术设计进行 DDD 落地实践进行了详细的讨论,但是还没有涉及到工程层面的落地.实际上所有的这些架构理 ...

  8. 领域驱动设计DDD和CQRS落地

    DDD分层架构 Evans在它的<领域驱动设计:软件核心复杂性应对之道>书中推荐采用分层架构去实现领域驱动设计: image 其实这种分层架构我们早已驾轻就熟,MVC模式就是我们所熟知的一 ...

  9. 微服务架构与领域驱动设计应用实践

    本篇文章一共分为三个部分,分别是微服务架构的演进过程.具体实践微服务的应用技术和领域驱动设计的意识转变.微服务架构已经渗透到互联网应用的方方面面,而领域驱动设计也逐渐被业界所接收. 微服务架构几乎都是 ...

最新文章

  1. python自定义随机数_python:numpy.random模块生成随机数
  2. jni头文件自动生成
  3. Excel-怎样实现行列转置
  4. 左击鼠标出现右击选项是怎么回事_跟着诗妍姐姐学电脑——鼠标
  5. gwt格式_活性GWT
  6. shell除去重复的行——uniq命令
  7. 生信宝典被分享最多的15篇文章
  8. 【Flink】Flink Max 和 MaxBy的区别
  9. 同宿舍的程序员毕业五年的现状:有人要当“螺丝钉”,有人头发掉光要出家...
  10. 《绝地求生》外挂源码被公布后,腾讯蓝洞再次出招打压!
  11. pythonexcel模块哪个好_Python-Excel 模块哪家强?
  12. 【十次方基础教程(后台)】Dockerfile脚本完成镜像的构建
  13. FTP,SFTP,FTPS,TF区别
  14. Dex2Oat执行参数总结
  15. 谷歌浏览器开发者工具network_关于Chrome谷歌浏览器开发者工具网络Network中返回无数据的问题...
  16. java 密码 星号显示_Java多线程 例子 cmd窗口下 实现输入密码星号显示
  17. Spread / Reast 操作符(...arr / ...obj)
  18. 基于python编程的激活码生成器
  19. 下着雨的星期天下午,年素清一个人走在外面
  20. 惊闻企业Web应用生成平台 活字格 V4.0 免费了,不单可视化设计器免费,服务器也免费!...

热门文章

  1. 外链代发切勿用群发器
  2. 西门子200恒压供水梯形图_S7-200 PLC控制的变频调速恒压供水系统设计
  3. Streamlit学习使用(一)
  4. 图嵌入Node2Vec安装
  5. php手机网页在线录音ios,iOS 录音实现
  6. STM32L4系列单片机的低功耗问题
  7. 提取lbp特征java代码_LBP特征提取原理及代码实现
  8. 使用 js 实现累乘之和
  9. Linux——终端和shell
  10. 啥是Python之禅