记得很多年以前读Evans的《领域驱动设计 – 软件复杂性核心应对之道》,那个时候DDD还很少人知道,更不用说实践了,这本书呢也在我的书柜里沉睡了很多年。而最近发现,不光传统重业务的软件公司,就连很多互联网公司也在推DDD。
然后呢,在不同地方听了一些DDD的分享,也看了一些DDD的博客,但整体下来,总有“新瓶装旧酒”之嫌。
究其原因:软件的方法论,到今天为止,前辈大师们已经给我们总结了太多太多:面向对象、SOLID原则、分层架构、SOA、微服务、业务建模、分析模式、设计模式、UML、4色建模、RUP 4 1、敏捷开发、CAP理论。。。
而DDD呢,其实是一个吸取百家之所长的方法论。在DDD的书里,你会看到业务分析、技术架构、设计模式等不同层次的东西。所以对于DDD的理解,需要深厚的理论和实践功底做基础,由此,你才可能把各式各样的方法论“糅杂”在一起,形成一个完整的体系。
而本文,则试图经由DDD把过去软件开发的一序列方法论串起来,最终让大家对DDD有一个系统化的认识,而不是纠结于DDD的各种细节概念。
到底什么样的软件适合用“领域驱动”
说到软件开发,下面是一些常见的套路: (1)小作坊式:纯粹需求驱动,或者说功能驱动,来一个功能做一个,不断累代码,系统越来越臃肿,到了一定程度实在受不了了,重构一把,然后继续,如此反复。。。 (2)大型流水线工厂:严格的软件过程,需求分析、业务建模、架构设计、详细设计、项目管理、测试用例编写。。人多、环节多、文档多。。 (3)技术控:做偏底层的,中间件、分布式存储、RPC框架等,主要关注点都是在技术层面,多线程、操作系统、数据结构、算法。。 (4)“大牛”驱动:有某位“大牛”在公司内部搞了一个框架,或者借助某个开源的框架,然后其他人接到需求,不断往里面填业务代码。。 (5)数据驱动:所有数据在一个DB或者中央存储里面, 所有工作以对数据的“增删改查”为中心。。
上面的分类方法呢,并不是完全的“正交分解“,很多时候做开发,是同时糅和了好几个套路。
那么DDD到底适合哪种呢?或者说,对于你当前的开发方法,是否应该考虑去学习吸收DDD呢?
在《领域驱动设计》的前言里面,明确说明了,要用DDD,有一个前提就是:“你的焦点是在领域和领域逻辑”。
“领域”是你最重要的部分
换句话说,如果你做的软件,领域不是最重要的部分。比如做底层系统,你的最重要的部分或者是存储,或者是解决分布式一致性,或者解决高并发问题。那可能你就不需要搞DDD。
但不要陷入一个误区:“重技术”的系统就不需要DDD。一个“重技术”的系统,可能同时也是一个业务非常重的系统,这样的系统,DDD也能发挥巨大价值。
“领域”要够复杂
如果你的领域比较简单,就数据库的CRUD就搞定了,那也不需要用DDD。但实际情况是,简单和复杂的分界线并没有那么清晰。可能一个软件你刚接手时,发现蛮简单的,但越做发现坑越多,越做发现越复杂。
另外,即使对同一个领域,不同人的看法也是不一样的,有的人会认为蛮简单的,有的人会认为很复杂,这也是一个很凭经验判断的事情。
关于这个呢,《实现领域驱动设计》里面开篇专门搞了一个打分卡,让你来衡量你的领域逻辑是否足够复杂到要用DDD。个人觉得这个打分卡是一个很不错的参照,让你对“什么是复杂”有一个直观的认识。
DDD核心要点之1: 统一语言,分析与设计不再2张皮
在传统的软件开发方法中,“分析”与“设计”往往是分开的。“分析”呢,不管技术如何实现,专注于业务分析,业务建模,建模人员呢,通常都不是程序员,而是领域专家; “设计”呢,根据业务建模结果,做架构设计,详细设计,然后进行编码实施。
这种办法经常遇到的一个问题是:领域专家、架构师、程序员不是在同一个“东西”上沟通。领域专家搞的业务模型,到了设计那,觉得这种业务模型很难实现,就会抛弃业务模型中的某些东西,等架构设计、详细设计搞完之后,这个业务模型可能就“束之高阁”了。
为此,DDD发明了一个词:Ubiquitous Language,让领域专家、架构师、程序员都用这同1种语言沟通,避免“牛头不对马嘴”的问题。那么这个UL语言,包含了哪些单词呢?
限界上下文/子领域; 实体/值对象/领域服务/领域事件; 聚合根/工厂/仓库;
DDD核心要点之2:吸百家之所长,为业务之所用
前面说了,DDD是一个很需要功底才能看明白的方法论,因为它糅合了软件方法论的各个层次的东西。要明白DDD,先的把下面列举的这些东西消化吸收。
架构模式
分层、六边形、SOA、CQRS、Event Sourcing
分析模式
详见Martin Flower的《分析模式》一书,里面总结了一些常见的业务模型的分析方法
设计模式
比如“工厂”,以前讲“工厂模式”,主要从技术角度去讲。而在DDD中,它把”工厂“的思想,应用到了业务分析上面。 当然,他在书中也说了,不是所有的”设计模式“都可以应用到业务层面,哪些能用,哪些不能用,没有一个标准答案。但这种”把技术模式应用到业务层面“的思想,非常值得借鉴。
DDD核心要点之3:DDD不关注什么东西?
DDD主要关注”领域“,但在实际软件开发中,除了”领域“,还有一些东西,对我们可能也很重要。
比如《软件架构设计》这本书中,提到的架构的5视图中,物理架构/运行架构在开发大型分布式系统中,也是至关重要。
在使用DDD做好业务分析的同时,我们也需要从技术角度去考虑这些问题。
如何实现DDD – 6步成诗法
在从宏观层面对DDD有了一个把握之后,接下来进入实施环节,讲解如何一步步实施DDD。
当然,下面的方法只是个人观点,每个人都可以根据自己的实践去形成自己的一个套路。
第1步:消化知识,梳理领域概念/流程
需求分析结束之后,我们知道了系统要做哪些功能。接下来就是进入DDD的第1个环节:梳理领域里面的所有”专业名词“,”业务流程“。
这个环节,考验的不是“技术”,而是你的业务sense,你的深入思考能力,说的再大一定,包括你的“情商”,你的沟通能力、表达能力。
在这个环节,你可能需要跟很多人沟通,产品经理、运营人员、领域专家、客户等等。对同一个“名词”,不同人可能会给你不同的答案,你需要去提炼每个“名词”背后的本质。
除了“专业术语”,对于核心的业务流程,你也需要梳理的很清楚。每个流程,参与者都有哪几方,分为几步,每步都有哪些业务规则。
第2步:划分子域
第1步做的差不多了,就进入第2步。这里要说明的是,并不需要第1步想到100%清楚之后,再进入第2步,这里会有个反复迭代的过程。
所谓划分子域,就是你并不需要为整个系统建一个统一的“大模型”,把所有概念都涵盖里面,这对脑容量的挑战会非常大。你可以按照高内聚、低耦合的原则,把领域划分成几个子域,每个子域有自己清晰的界限,分而治之。
在划分好之后,你可以再回去优化第1步,进一步在每个子域里面,提炼概念。
第3步:每个子域里面找“聚合根”
子域有了,现在每个子域里面有一堆 “概念”,平铺开,平等对待每个“概念“,太琐碎。 接下来,在这一堆”概念“里面,找几个”老大“出来,也就是聚合根。每个老大带几个小弟。
第4步:聚合根之间引入“领域服务”
每个子域里面,有了几个老大。老大与老大之间,如何互相协作呢?
比如现在有一个业务流程,需要多个老大,每个老大带的团队都要做一部分事情。你可以让某个老大负责协调,也可以在搞一个”协调者“,由它统一协调多个老大。
这个“协调者”,就是领域服务。
第5步:“领域服务”之间/ “子域”之间引入“领域事件”
有了“领域服务“,“领域服务”和“领域服务”之间就可以引入“领域事件”,一个流程做完了,发一个事件出去,另一个接收到事件之后,开始下一个流程;
同样,每一个“子域”,在实现层面,就对应一个SOA服务。它们之间也可用“领域事件”串起来。
第6步:架构重设计
在前面5步做完之后,我们基本在实现层面,确立了多少个SOA服务?每个服务内部多少个聚合根?多少个领域服务? 服务之家多少个“领域事件”?
但这种设计,过于偏重“业务”,还没有考虑要解决的一些“技术难题”,比如拆了多个服务/多个DB之后,查询的效率问题? 比如发事件,如何保证事件的不丢不重?
这就是接下来要说的CQRS和分布式事务:
CQRS
CQRS全称是Command Query Responsibility Separation,也就是读写分离。
拆了之后,不是查询的慢吗,那可以实现读写分离,比如把所有的写的DB数据,全部进入到搜索引擎里面,所有的查询都在搜索引擎里面解决,领域模型只负责写入问 题。
不用搜索引擎,用DB也是一样的思路。另外搞一个DB或者表,把查询的数据,提前在这join好,查的时候就不用再把分散的数据拼装在一起了,这也就是“重写轻读”的思路。之前我在另1篇博客 “分布式架构–思想与理论–基本思想汇总里面”已经谈了这个问题。
分布式事务
消息的不丢不重是解决分布式事务的一个关键,关于分布式事务的问题,我在另一篇博客 “分布式消息队列RocketMQ–事务消息–解决分布式事务的最佳实践”里面有详细探讨,此处不再详述。
下面以图的形式来形象的展示一下上面6步最终形成的结果:实体/值对象由聚合根管理;聚合根之间串成领域服务;领域服务之间用领域事件通信;领域服务组成子域;子域之间用领域事件通信。
最后
本篇没有详细的去讲DDD的各种细节,而是想让大家对DDD有一个宏观认识,知道如何去看待DDD?DDD可以干什么,不能干什么? 知道DDD和传统的各种软件方法论,有一个什么样的源渊?
关于DDD里面的各种术语,各种细节,后面有机会一一讲述。