上章回顾:
回顾了软件架构设计七大原则,DDD的核心思想软件架构是现实世界的映射,初步介绍了DDD的基本核心概念.
DDD常用方法
问题背景
DDD(Domain Driver Design):领域驱动设计,它有三个关键字:领域,驱动,设计 领域,去定是探索业务的边界;驱动,表示前者是后者的决定性因素;设计,包括产品设计,UIUE设计,软件设计。从三个关键字定义可以得出,领域是关键,DDD是先领域后设计,并且通过领域划分来指导设计落地。
实际上,大多数软件开发人员有种思维定势,是先技术后产品,拿到需求后,不管三七二十一立马开始编码实现,所以不管是多么有经验的工程师,也无法实现优秀的软件系统。在这种思维定势下:
- 忽略需求的细节,你真的读懂需求了吗?
- 单点式思维,虽然实现了需求功能,看上去很完美,时间也控制的很好,当时带来了很多风险点,我们称之为技术债务.
- 有些情况下,也存在过度设计,过度分层,这样也大大的增加了系统的复杂度和实现的复杂度,有时候往往最简单的设计才是最有效的设计.
针对以上问题,我们需要加强对需求的理解,也就是加强对业务的理解,技术总是要服务于业务的,特别是作为架构师,需要站在更高的层面进行软件架构设计,不过度设计,尽量找到设计和实现的平衡点.多思考,慢思考,多画设计图,关注架构的可扩展性.DDD中提出了一些方法论用来指导软件架构设计.
如何实践DDD
实施DDD一般有两步,并且需要开发,产品和领域专家的通力合作。为了实施速度有所保障,还有一些项目加速和项目管理工具.一般DDD分为战略设计,战术设计两个阶段.
战略设计
战略设计,通过划分领域,以及处理各个领域之间的关系,首先搭建了业务思想上的总体框架。这个阶段重点关注
限界上下文领域模型
限界上下文,原文说它是语义和语境上的边界,我的个人理解是,它是在描述组织交付出来,面向客户的交付边界。确定了边界以后,需要做好边界内的业务和服务,并定义统一的清晰的接口对外提供服务,什么意思? 就是边界内部提供什么功能,怎么实现外部不关心,对边界外的外部系统是透明的,定义和维护好对外提供的接口或者服务,通过这些接口或者服务来实现外部系统的访问和连接.
一个系统中,最核心的限界上下文被称为核心域。通常除了它,还有通用子域和支撑子域。通用子域是很成熟的业务,比如使用的存储中间件服务等,这些都是有现成的解决方案,比如搜索子域可以通过ES来支持;支撑子域通常没有现成产品,但是它没有核心域重要,这个领域内需要关注投入产出比ROI,比如大多数公司的数据库中间件是在开源产品上做了一些定制开发和维护。
限界上下文这个概念的目的是为了在业务扩展的时候,防止向领域内注入新概念,导致业务变得没有边界,领域内提供高内聚的服务,结合单一职责,让领域内实体尽可能全局唯一。减少与其他领域的耦合
在这一步,DDD要求以领域专家意见为准。当实施了DDD方法以后,不论是领域专家还是开发,都应该拒绝向领域注入与业务无关的概念,只有把这些技术概念放到业务之外,我们的业务核心往往才能足够集中,易于迁移,最后通过抽象确保扩展性。
限界上下文通用语言
当我们有了业务的限界上下文以后,就需要在这个限界上下文中发展一种语言用于表达软件模型,这个语言就叫做这个限界上下文里的通用语言。它可以是任何计算机语言、人类语言或者图形,只要能让团队内的每个人都能看懂。
通用语言包括设计中的技术术语,技术架构模式,领域模型术语,业务术语等等.通常我们说的用例图,流程图,类图,状态图,时序图都可以,通过实现语言无关的各种图,表,文字等形式,让开发和业务,以及领域专家都可以快速理解并达成一致.
子域处理遗留系统
对于遗留系统,这些遗留系统的边界并不清晰。因此我们会将遗留系统放到一个子域里,把它们的问题放到我们的设计之外。这一步做完后我们的图案与之前没有本质上的区别,无非是多了一点子域。
上下文映射集成多个限界上下文
上下文映射是两个限界上下文之间的连线,表示两者之间的关系.通常来说,不同的限界上下文是不同的团队维护,那么此时它也代表着两个团队之间合作的关系。
常见的映射关系是RPC接口(同步)。限界上下文之间使用RPC通讯是有风险的,首先会有网络风险,同时也意味着两个限界上下文之间存在耦合。另外一种限界上下文映射关系采用事件的订阅(异步),异步存在延时问题,这就要求领域专家在设计的时候要考虑不同领域之间通知的延迟对于业务的影响,以及如何消除影响。
战术设计
战略设计好了,就可以利用这些软件概念把业务逻辑映射成软件架构,这种过程DDD称为战术设计。战术设计主要涉及到把一些实体和值对象放一起,称为聚合设计.利用领域事件设计解决系统之间的通讯。
聚合设计
一个限界上下文里通常有多个聚合,聚合逻辑上是相对独立的。在DDD实践中,聚合是事务的边界;聚合之间并不保证事务,只能用最终一致性。任何需要事务保护的逻辑都应该在一个聚合内。在限界上下文里,将其他聚合能力整合在一起对外提供能力的聚合,被称为聚合根;其他聚合也被称为实体。
此外,一个限界上下文里还有值对象,它也代表了某种相对独立的概念。怎么区分实体和值对象呢?这取决于业务。如果一个对象,具有多种动词去操作它,那么它应该是一个实体,存在生命周期;如果一个对象,在系统里只是被传递而没有业务逻辑,那么它就是值对象。
由于聚合是事务的边界,那么每个聚合在设计阶段,最重要的是找到业务的不变性,因为其他模型都参考聚合的根。所以要想改变其他对象,只能通过聚合的根去操作。在事务提交前后,关注数据的约束条件,数据的整体性,一致性等。
领域事件设计
我们说聚合之间要保持数据一致性,而通常的做法是采用领域事件实现最终一致性。
领域事件里通常至少包含业务动作和其业务参数,也可以增加更多的下游关注的事件信息,避免下游为了完成处理还需查询。
领域事件会持久保存在专门的数据表中,用来表示领域事件的因果关系。
有一种专门的存储方式是事件溯源,它不需要存储数据当前是什么,而是从历史事件中按顺序应用重建,得到当前的数据。这样写入时的成本只有校验后持久化,也没有增加和删除的能力。如果事件很多,性能问题很大,也可以加上缓存和快照,优化性能。
事件风暴
事件风暴是快速的设计技术,让领域专家和开发人员都可以参与学习,目的是在有限的时间里尽可能多地完成设计,也就是加速设计阶段。\
事件风暴要先做如下准备:\
- 邀请领域专家和开发人员
- 每个成员都应该以开放的心态参与讨论,不必追求正确和速度。
- 各种颜色便利贴,正方形的。一般一个便利贴只会写几个词。
- 每个人都有黑色的马克笔。
- 最好有一面至少10米长的墙并且铺上白纸。最好建模的几天时间内保持每次讨论的结果一直保留并供下次讨论使用。
事件风暴的基本步骤:
1、在便利贴上写领域事件,梳理出业务流程,一般是橘色。 - 创建领域事件强调我们首要关注的是业务流程,而不是数据和结构
- 把每个领域事件写在一张便利贴上,应该是动词的过去式。
- 把写好的便利贴按照时间顺序放到建模平面上,从左往右逐步发生。
- 并行发生的领域可以上下排列,不明白时机的事件可以单放在某个单独的位置。
- 如果发现了问题点,可以用红色的便利贴上,并用一段文字解释是什么问题。
- 领域事件最终会触发一个执行的流程,每个流程都应该命名并记录在浅紫色的便利贴。需要从领域事件画个箭头指向这个流程。支队核心域中非常重要的细粒度事件进行建模。 2、创建导致领域事件发生的命令,命令应该是指令式的。
- 创建领域事件的便利贴是浅蓝色的。
- 触发事件的便利贴放在触发的事件左边,会有很多成对出现的命令和事件。但是也有不是命令触发的事件,比如时间触发的事件。
- 如果存在一个执行动作的特定角色,那么可以在命令左下角使用亮黄色的便利贴记录角色名称。
- 命令也可以触发流程。
- 在命令和事件之间画出线条
- 按照时间顺序,将命令和事件的关系处理好
- 一个命令可以带来多个事件
3、把命令和领域事件通过实体、聚合联系起来。由于建模没完,因此没有真正的实体和聚合,而是领域专家思想里的业务概念和概念群。用淡黄色的便利贴来表示聚合,其左下角是命令,右下角是事件。聚合的名字应该是名词。
4、在建模平面上画出边界和事件流动的箭头。
5、识别用户执行操作所需的各种视图,以及客户不同用户的关键角色。\
其中4和5是事件风暴的关键。
章节总结
工欲善其事,必先利其器,本章节主要介绍如何使用DDD,包括战略设计,战术设计,以及Evans提到的事件风暴工具,接下来的章节讲结合具体实际案例进行剖析.