架构设计篇之领域驱动设计(DDD)
本篇文章基于Eric Evans作者和翻译作者孙向晖,霍泰稳的书,做的软件思想笔记,向作者们致敬。
不要畏惧你所不知道的领域,如果你有需要,那么就搞定它。
- 刘晓成@小诚信驿站
一、定义
领域驱动设计的定义:
领域驱动设计,其实就是业务驱动设计,通过软件开发架构师和开发人员与最熟悉该领域的业务专家一起勾勒出需求领域模型去实际解决业务场景遇到的问题。
二、核心
DDD核心是什么?
DDD核心是关注精简的业务模型及实现的匹配。
三、为什么需要DDD?
DDD希望解决什么问题?
目前我们使用的面向对象程序设计,也有面向过程程序设计,为什么还需要领域驱动设计?
设计之选择性的三个问题
设计之封装边界的问题
设计之业务和研发思维问题
解决方式:看清楚我们提炼出的模型,在整个架构和整个开发过程中所处的位置和地位。
解决方式: 哪些具有什么样素质和技能的人来处理模型和对象实现,应该用什么样团队模型来匹配业务模型
解决方式:尽信书不如无书,切勿生搬硬套,要有自己的方法论,进行切合实际场景的使用。
四、DDD方法论
4.1、理解你的领域,熟悉你的业务
所有的技术实现都是围绕业务场景开展,在一个系统设计之前一定要充分理解你的应用场景。业务的需求(痛点),你要从哪个切入点(抓手)开展?从根本上看,你解决了什么问题?
我们比如是滴滴的客服系统,则我们应该更好的记录用户的问题,解答客户的问题,放弃商业化的售卖东西。
我们比如是淘宝的运营系统,则我们应该更好的记录用户的购买转化率,点击率,曝光率,直接下单率,双11同环比GMV,以及净利润营收等,而不应该关注用户是否可以用滴滴APP打到车,美团APP是否可以下单订到外卖。
3.1、比如将模型图形化:图、用例、画和图片等(UML少量元素的场景来描述)。
3.2、针对要交流的领域内的特定问题建立一种语言(prd原型图)
3.3、用代码作为语言沟通,优秀的高可维护性的代码(伪代码来约定)
4.2、构建领域知识,实践你的业务
对于你的业务和你不熟悉的领域,都需要从0-1开始构建你的业务领域知识体系。
与领域专家交谈,比如淘宝的运营系统是和运营和商家交谈,比如滴滴客服系统是跟滴滴的客服和司机乘客交谈。你需要尽可能多地从专家处学 习领域知识。通过提出正确的问题,正确地处理得到的信息,你和专家会开始勾勒领域的骨架视图,也就是领域模型。这种骨架视图既不完整也不能保证是正确的,但它却是你需要的开始点,可以尽 力判断出领域的基础性概念。比如美人鱼电影中的警察根据报警人员的描述,勾勒出美人鱼的画像(

2.构建时间损耗,多次沟通不耐其烦
软件架构师和开发人员和领域专家,会在一起创建领域的模型,可能这个过程需要耗时很久很久,但是为了目标最后的建模的准确性,这个
4.3、模型驱动设计,模型设计完成编码
4.3.1、我们应该如何动手处理从模型到代码的转换
从分析模型出发,中间可以过程化编程(函数调用和数据结构算法来表达),如果是复杂的模型驱动,则不建议过程化编程,可以伪代码实现。
4.3.2、模型驱动设计的基本构成要素
4.3.2.1、分层架构设计之逻辑清晰(MVC)


上面的的4个概念层其实就是我们所说的view- controller- service(domain)- dao(mapper),是不是非常符合我们的MVC架构。
4.3.2.2、实体与值对象(entity与各种O)
建议选择那些符合实体定义的对象作为实体,将剩下的对象处理成值对象,没有标识符,值对象就可以被轻易地创建或者丢弃。没有人关心创建一个标识符,在没有其他对象引用时,垃圾回收会处理这个对象。
一条箴言是:值对象就是我们所说的dto或者vo,根据entity或者domain实体产生的多个副本(派生的对象)。

4.3.2.3、服务(系统服务)
一个服务应该不是对通常属于领域对象的操作的替代。我们不应该 为每一个需要的操作来建立一个服务。但是当一个操作凸现为一个领域中的重要概念时,就需要为它建立一个服务了。
以下是服务的3个特征:
1. 服务执行的操作涉及一个领域概念,这个领域概念通常不属于一 个实体或者值对象。
2.被执行的操作涉及到领域中的其他的对象。
3.操作是无状态的。
4.3.2.4、模块(功能模块)
对一个大型的复杂项目,模型趋向很大。模型到达了一 个作为整体很难讨论的点,理解不同部件之间的关系和交互变得很 困难。将模型组织进模块。模块被用来作为组织相关概念和任务以便降低复杂性的一种方法。
4.3.2.5、聚合(对象的封装组合)
聚合是一个用来定义对象所有权和边界的领域模式。工厂和数据库,用来帮助我们处 理对象的创建和存储问题
一个模型会包含众多的领域对象。不管在设计时做了多少考虑,我们都会看到许多对象会跟其他的对象发生关联,形成了一个复杂的关系网

4.3.2.6、工厂(工厂方法和抽象工厂)
工厂方法和抽象工厂,只关心对象的创建和强化所有的不变量,返回对那个对象的引用或者拷贝
4.3.2.7、资源库(数据库)
从数据库或者new创建到删除或者gc回收的过程
使用一个资源库,它的目的是封装所有获取对象引用所需的逻辑。领域对象不需处理基础设施,以得到领域中对其他对象的所需的引用。只需从资源库中获取它们,于是模型重获它应有的清晰和焦点。
资源库会保存对某些对象的引用。当一个对象被创建出来时,它可以被保存到资源库中,然后以后使用时可从资源库中检索到。如果 客户程序从资源库中请求一个对象,而资源库中并没有它,就会从 存储介质中获取它。换种说法是,资源库作为一个全局的可访问对 象的存储点而存在。
资源库可能包含一定的策略。它可能基于一个特定的策略来访问某个或者另一个持久化存储介质。它可能会对不同类型的对象使用不 同的存储位置。最终结果是领域模型同需要保存的对象和它们的引 用中解耦,可以访问潜在的持久化基础设施。(如下图式例)

资源库的接口是纯粹的领域模型(比如根据业务场景实现的查找用户或者添加用户的功能)

4.3.2.8、规约(比如API约定,接口文档,交互协议等)
指定一个查询条件,规约允许定义更复杂的条件。来提供服务支持。
4.3.3、面向深层理解的重构
基于模型驱动设计从模型我们开发用代码进行了表达,以及对于业务领域进行了基本构成拆分形成了一个服务或者多个服务系统。正如我们之前所说的领域模型初期不一定非常完美切合业务场景。那么随着不断深入了解业务以及功能逐渐完善,重构也是在这个过程中必不可少的环节。持续重构将是保证我们的好的方案以及应对变化的需求的保障护航舰队。
一个好的模型产生于深层的思考、理解、经验和才能 。
4.3.3.1、持续重构
设计方案应对需求的变化
4.3.3.2、凸显关键概念
业务领域建模的时候,约束(限制条件,校验参数)、过程(实现业务的逻辑过程)和 规约(约定的接口文档交互方式等)这些是一些关键的核心概念,而这些需要我们不断完善,但并不是要经常变动的。
4.3.3.3、保持模型一致性(每个领域保证业务领域模型与整体一致)
之前我们说过,针对业务专家描述的业务领域可能会有部分需要独立部署服务系统,那么这些单独部署的服务系统,从整体上来看应该都符合整体业务领域模型的内容。
划清边界,界定上下文
比如分层领域,需要界定应用上下文,每个系统服务的职能。
持续集成
持续集成是基于模型中概念的集成,然后再通过测试实现。任何不完整的模型在实现过程中都会被检测出来。持续集成应用于界定的上下文,不会被用来处理相邻上下文之间的关系。
上下文映射
(Context Map)是指抽象出不同界定上下文和它们之间关系的文 档,它可以是像下面所说的一个试图(Diagram),也可以是其他任何写就的文档。

共享内核(共享基础服务)
共享内核的目的是减少重复,但是仍保持两个独立的上下文。对于 共享内核的开发需要多加小心。两个开发团队都有可能修改内核代 码,还要必须整合所做的修改。如果团队用的是内核代码的副本, 那么要尽可能早地融合( Merge)代码,至少每周一次。还应该使 用测试工具,这样每一个针对内核的修改都能快速地被测试。内核 的任何改变都应该通知另一个团队,团队之间密切沟通,使大家都 能了解最新的功能。

客户和供应商---实际上是上下游系统
顺从者(如何应对内部需求堆积,合理协配外部需求)
防奔溃层(实际上指的是如何应对模型针对不同外部需求的服务变化可以用门面模式,适配层处理)

独立方法(独立服务部署)
实际上指的是如果我们的领域模型 不相关的服务内容可以作为独立的服务部署和实现。
开放主机服务(提炼核心服务,核心业务)
系统的核心域要看我们如何理解系统。精炼模型。找到核心域,发现一个能轻松地从支持模型和代码中区 分核心域的方法。强调最有价值和特殊的概念。使核心变小。
4.3.4、实践DDD的方式
五、DDD的思维陷进