背景概诉
刚开始接触领域驱动设计是在4年前,主要原因是对于之前业务中的微服务化的架构升级缺少系统、科学的“方法论”。虽然接触微服务开发、服务拆分也有好多年了,但是对于单体应用到微服务的改造,之前都是根据个人的经验和组织架构进行的拆分。整体上谁说没啥大问题,但是却没有理论性的“沉淀”。在云原生架构的驱动下,接触到了领域驱动设计。先后探索了《领域驱动设计:软件核心复杂性应对之道》、《领域驱动设计模式、原理与实践》、《实现领域驱动设计》等著作,期间也参加过多个工程实践交流、研讨会。对于其作用和价值,是非常认可的,可谓是受益匪浅,是每一个程序员需要掌握的“内外兼修”的武功秘籍。在后续服务拆分、系统重构的规程中,也逐步加深了其核心“秘诀”。
温故知新
在开始今天的分享之前,我们先回顾下其基础知识,方便我们展开讨论和思考。我们将从“形”和“神”两方面展开讲解。为什么“形”在前,“神”在后?因为很多同学在接触领域驱动设计的时候,关注太多了之后,会逐渐的迷失自我,而且很多内容是实践完之后才能逐步掌握要义。我们强调“形”在前,“神”在后,是给大家预留在后续的实践中基于稳定架构和Facade模式自我进化,避免因为重构而导致的工程进度影响太大。
- 形
- 四层架构
- 架构特点
- 每层只能与位于下方的层发生耦合
- 每一层各司其职,并且只关心向下一层的实现,而不会出现各层耦合。
- 职责划分
- 表现层: 和客户的交互(用户<->应用层),数据的组装、格式转换,Facade接口层
- 应用层:实现服务的组合和编排,主要面向用例和流程相关的操作。是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排
- 领域层:实现领域的核心业务逻辑
- 基础层:基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。
- 架构特点
- 架构总结
虽然架构简单(切合架构设计原则之一的简洁性和清晰性),但其模块化、松耦合、易维护等特性,足以满足大多数的业务诉求,是构建复杂应用程序的一种有效方式。除此之外,在我们的工程实践中,多数服务均采用了该架构,对于不同组织架构之间做业务交流、代码的交叉Review、人员调整提供了莫大的便捷。同时,也节约了前期架构设计、学习成本。
- Facade模式应用 - 门面层隔离前台和后台系统,定义特定于表现层的数据结构,从后台获取数据内容并转化为表现层的数据形式。
-
服务拆分 - 新建服务,启动一个进程,尽早的注册到注册中心,开始提供服务,这个时候,新的服务中的代码逻辑可以先没有,只是转调用原来的进程接口。
-
代码开发 - 从表现层中分离出专门的门面层,具有下面的优势:
- 使得表现层能够独立于后台系统,与后台系统并行开发 表现层通过门面层接口达到和应用层、领域层解耦,意味着表现层可以独立开发,不必等待后台系统的完成,亦不受后台系统重构的影响,在需求调研阶段系统原型出来并得到用户确认之后,就可以开始表现层的开发了。
- 把事务作用范围控制在后端,缩短事务的跨度,提升性能和系统的吞吐量。 事务要跨越服务器的边界,复杂性增加,性能严重下降。门面层的存在使得实体和事务都限制在后台系统,不需要扩展到前台服务器。
-
Facade总结
-
使用Facade模式可以帮助简化微服务之间的交互、降低微服务之间的耦合度,并提高整体系统的易用性、安全性和可维护性。在工程实践中,Facade模式对我们的服务拆分、新服务开发、系统重构带来了极大的便利性,这也是为什么单独把Facade作为一个独立的小节展开讲解。
- 总结
通过诸如实体、值对象、领域模型、仓库服务、领域服务、分层架构塑造一套标准的微服务形态,犹如标准化的模板,让开发更加关注业务价值本身,而不是推动业务落地的技术细节。不仅提升了生产效率,还规范了业务形态和技术架构。非常适合刚接触微服务拆分、微服务开发的同事。
- 神
- 问题域 - 核心、通用、支撑
初次看到问题域的划分时,大脑陷入了沉思,这三个子域的划分似曾相识,仔细搜索后,一些关于系统架构设计的原则映入眼帘:
- 关注核心功能:架构设计应该专注于支持和优化核心业务功能,确保系统的架构能够有效地推进业务价值落地。通过关注核心业务流程,企业可以将有限的资源和精力投入到最能体现其竞争优势的领域,从而提升核心业务的质量和效率。专注核心业务可以使企业更加灵活,更好地适应市场变化,因为精力不再分散在非关键领域。
- 分离关注点:将核心业务逻辑与非核心功能分离,以便于更好地管理和维护系统。剥离非核心业务还可以减少企业在非关键领域的投资和开支,帮助企业降低成本和提高效益。
- 模块化:将核心业务功能模块化,以便于重用、扩展和替换,同时将非核心功能模块化并可能外包给其他系统或服务。
- 松耦合:确保核心功能模块之间的耦合度尽可能低,以提高系统的灵活性和可维护性。
- 最少知识原则:模块间的通信应该尽可能少,以防止非核心模块对核心模块产生不必要的依赖关系。
这说明了什么?领域驱动设计是科学的!
- 界限上下文
- 主打就是一个“自治”
- 最小完备 - 是指自治单元履行的职责是完整的,无需针对自己的信息去求助别的自治单元,这就避免了不必要的依赖关系。
- 稳定空间 - 指的是减少外界变化对限界上下文内部的影响。
- 自我履行 - 由自治单元自身决定要做什么。从拟人的角度来思考,就是这些自治单元能够对外部请求做出符合自身利益的明智判断,是否应该履行该职责,由限界上下文拥有的信息来决定。
- 独立进化 - 指的是减少限界上下文的变化对外界的影响。
- 微服务与界限上下文的关系
- 微服务是由松耦合的、SOA架构的界限上下文组成的体系结构
- 业务上,微服务应完全与界限上下文对齐
- 一个微服务应涵盖至少一个界限上下文
=》关于这点,需要特别的注意,避免“空壳”应用的产生(避免的方法简单粗暴,多放几个界限上下文在一个微服务中)。产生原因有两点,一方面高估了业务的发展,另一方面过度追求“可拓展性”,导致服务拆分的过程中,有些应用只是一个“空客”,没有核心的业务逻辑,只是一些简单的DO/DTO的封装。这会导致什么问题呢?
- 开发成本高 - 此类应用无非是增加了服务链路的长度,一个简单字段的变更都需要重发
- 资源利用率 - 资源利用率低,一个调用、封装,只是编码、解码工作,无业务价值
- 组织架构与限界上下文
- 推动组织架构的调整对于大多数的企业来说,都是比较困难的,实践中的教训告诉我们,还是向组织架构靠拢吧。
拆分践行
- 理论到实践的“桥接” - 界限上下文的发现
这块内容可以说是大家最为关注的,也是最为棘手的环节了。江湖上一直流行着“理论派”和“实践派”。理论派推崇“无为而治”,强调领域驱动设计是一门内功心法,没有“招式”的约束和流程。实践派推崇“按部就班”,以事件风暴+四色建模为主要的方法。从笔者自身角度触发,更倾向“理论派”。在OO的思想驱动下,很多领域驱动设计内容都是水到渠成的。但是,就像前文所讲,领域驱动设计是集综合性、复杂性、实践性为一体的设计思想。多数同学在开始接触时,可以说是无从下手,不知所措。这也是为什么前面介绍“形”为先。
在工程实践中,实践派的方法论我们也实践过,效果不是很理想。根本原因有几点,例如一线员工参与热情不高、思维过于发散、时间成本高、输出产物没价值等等。
那么,有没有更好的方式方法呢?上面的活动图,清晰展示了各个参与者、系统在流程中的职责以及用户活动之间的流程和控制流,能够我们帮助更好地理解系统中各部分的活动、交互以及操作流程。是的,这一种基于“核心业务”活动图的最佳实践。其涵盖了业务、系统、用户,将核心业务的各个组成要素清晰的映射到组织、服务、功能。在整个实践过程中,可以说既高效,又科学,具有很强的可落地性。
结合实践,我们把步骤再概括下:
-
确定核心用户活动
• 列出主要用例的核心活动
Activity | Business Function | Actor | |
---|---|---|---|
1 | activity1 | function1 | actor1 |
2 | activity2 | function2 | actor2 |
•识别每个活动的业务功能/能力
•确定此活动的用户角色(参与者)
•确保活动列表完成整个业务解决方案
- 根据上述的核心活动,映射到界限上下文
- 细化领域模型设计
到这里,相信很多同学都有了一个清晰的轮廓,但是仍有同学会问,从哪里开始呢?
不妨从一个简单的use case着手吧。
最后,还有一个要点,尽最大能力的快速交付产品并听取反馈 - 敏捷迭代。
重构焕新
关于重构,没必要抱怨和吐槽,前人做的已经足够多了。业务的发展和技术变革,实在是太快了。尽管我们一直在努力,例如努力的做好架构的可拓展性,可业务发展却和技术出现了背离。预留的服务,几年过去了还是个“空客”。当初设计的“充血”模型,如今早已不堪重负。重构,一直在路上。本节主要讨论如何解决BBoM的问题,通俗点来讲,向着“屎山”出发。
BBoM(Big Ball of Mud)大泥球模式,我们系统或软件架构通常没有明确结构或规划,代码和架构混乱不堪,难以维护和理解。当然了,这也是最普遍、最受欢迎的模式。
- 对象定义与流转
这是很基础,却容易被忽略,非常重要的内容。为什么?在重构的过程中,我们发现许多业务逻辑其实很简单,多是请求封装、服务调用、结果处理。但是其实现却非常的晦涩,甚至是不知其所以然。因为在这个过程中,搞不清楚对象的定义和声明周期,导致类似的对象这里拷过来,那里拷贝过去。折腾了一层又一层,毫无意义。上文中,我们讲到领域驱动设计的原则的只依赖下一层,不是让大家每层都定义一个对象用来传递业务语义的。这里推荐业务语义+冗余拓展模式。这种设计的优点比较明显,易维护,开发效率高,缺点是需要手动控制部分属性的可见性。工程实践中,内部系统的调用都可以忽略可见性的设计,对外,在敏感信息加密的保护下,也无需过多担心泄露问题。
public class BusinessObject{
private String property1;
private String proprtty2;
}
public class ABusinessObject extends BusinessObject{
@JsonIgnore
private transient String property2;
}
- DO - 贫血与充血之争
诸如validate、compare工具方法类可以放在DO中,其他的业务逻辑不建议放在DO中。
为什么?
在整个重构的过程中,很大一部分工作是处理DO中的方法。多、杂、乱,是最大的感受。很多同学已经分不清究竟是DO的职责,还是DS的职责了。在这种情况下,依赖关系更是雪上加霜,加剧了业务的复杂性。背离了DO/DS原本的职责。原因两方面,一是业务的迅速发展;二是有些同事不了解这套设计模型。推荐放到DS中的原因结合实际情况来说,拓展性好,方便维护。
- 关注核心流程,剥离非核心流量
一方面,我们对于RT非常敏感,另一方面非核心业务的流程影响排障的效率。我们把非核心的业务使用异步/消息的模式,从核心业务流程上剥离了出去。不仅提升了RT还减少了核心链路的维护成本。
- 数据同源 - 不在详细讲解,强业务属性
- 弱化概念 - 领域驱动设计有太多的概念了,过多的关注这些概念,会影响我们实践的效率。正如下文说说,领域驱动的核心是什么? O.O
思考总结
“ When you remember that DDD is really just ’OO software done right‘, it becomes more obvious that strong OO experience will also, stand you in good stead when approaching DDD. ”
- 很多概念和定义都已忘却,唯独这句话刻骨铭心。
领域驱动设计强调将业务领域的复杂性融入到软件设计和实现中,通过建立贴近业务的领域模型、团队协作和持续演化的设计,以确保软件系统能够准确地反映业务需求,并具有较高的可维护性和扩展性。我们再来回顾下设计要点:
- 强调业务领域为核心:DDD鼓励开发团队深入理解和建模业务领域,将业务概念和业务逻辑直接映射到软件设计和实现中,以确保软件系统能够准确地反映业务需求。
- 模型驱动设计:DDD强调建立一个贴近业务的领域模型,这个模型应当是对业务问题和解决方案的抽象表示,能够为开发团队和业务人员提供共享的语言和理解。
- 分层架构:DDD鼓励将系统划分为不同的层次,如领域层、应用层和基础设施层,以便更好地管理复杂性,并保持领域逻辑的纯粹性。
- 持续演化的设计:DDD认为领域模型应当是一个持续演化的过程,随着对业务理解的深入和需求的变化,领域模型也应当不断地进行调整和改进。
- 团队协作和沟通:DDD鼓励业务人员、开发人员和其他利益相关者之间的密切合作和沟通,以确保软件系统的设计和实现能够准确地满足业务需求。