领域驱动设计在工程实践中的“舍与得”

背景概诉

刚开始接触领域驱动设计是在3年前,主要原因是对于之前业务中的微服务化的架构升级缺少系统、科学的"方法论"。虽然接触微服务开发、服务拆分也有好多年了,但是对于单体应用到微服务的改造,之前都是根据个人的经验和组织架构进行的拆分。整体上谁说没啥大问题,但是却没有理论性的"沉淀"。在云原生架构的驱动下,接触到了领域驱动设计。先后探索了《领域驱动设计:软件核心复杂性应对之道》、《领域驱动设计模式、原理与实践》、《实现领域驱动设计》等著作,期间也参加过多个工程实践交流、研讨会。对于其作用和价值,是非常认可的,可谓是受益匪浅,是每一个程序员需要掌握的"内外兼修"的武功秘籍。在后续服务拆分、系统重构的规程中,也逐步加深了其核心"秘诀"。

温故知新

在开始今天的分享之前,我们先回顾下其基础知识,方便我们展开讨论和思考。我们将从"形"和"神"两方面展开讲解。为什么"形"在前,"神"在后?因为很多同学在接触领域驱动设计的时候,关注太多了之后,会逐渐的迷失自我,而且很多内容是实践完之后才能逐步掌握要义。我们强调"形"在前,"神"在后,是给大家预留在后续的实践中基于稳定架构和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)大泥球模式,我们系统或软件架构通常没有明确结构或规划,代码和架构混乱不堪,难以维护和理解。当然了,这也是最普遍、最受欢迎的模式。

  • 对象定义与流转这是很基础,却容易被忽略,非常重要的内容。为什么?在重构的过程中,我们发现许多业务逻辑其实很简单,多是请求封装、服务调用、结果处理。但是其实现却非常的晦涩,甚至是不知其所以然。因为在这个过程中,搞不清楚对象的定义和声明周期,导致类似的对象这里拷过来,那里拷贝过去。折腾了一层又一层,毫无意义。上文中,我们讲到领域驱动设计的原则的只依赖下一层,不是让大家每层都定义一个对象用来传递业务语义的。这里推荐业务语义+冗余拓展模式。这种设计的优点比较明显,易维护,开发效率高,缺点是需要手动控制部分属性的可见性。工程实践中,内部系统的调用都可以忽略可见性的设计,对外,在敏感信息加密的保护下,也无需过多担心泄露问题。
scala 复制代码
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鼓励业务人员、开发人员和其他利益相关者之间的密切合作和沟通,以确保软件系统的设计和实现能够准确地满足业务需求。
相关推荐
孤狼程序员3 小时前
【Spring Cloud 微服务】5.架构的智慧枢纽:深度剖析 Nacos 注册中心
spring cloud·微服务·架构
老顾聊技术4 小时前
网关如何聚合各个微服务的接口文档?
微服务·api
阿里云云原生4 小时前
使用 MSE 流量防护轻松面对运行态流量不确定风险的最佳实践
微服务
亦安✘5 小时前
服务器从0到1微服务所需的环境的安装
运维·服务器·spring cloud·微服务
3Cloudream7 小时前
互联网大厂Java面试实录:Spring Boot与微服务架构解析
spring boot·微服务·hibernate·jwt·java面试
微扬嘴角17 小时前
springcloud篇5-微服务保护(Sentinel)
spring cloud·微服务·sentinel
天上掉下来个程小白17 小时前
微服务-25.网关登录校验-网关传递用户到微服务
java·数据库·微服务
叫我阿柒啊1 天前
Java全栈开发面试实战:从基础到微服务的深度解析
java·jvm·微服务·vue3·springboot·全栈开发·restfulapi
guojl1 天前
Gateway源码分析
后端·微服务
007php0072 天前
Jenkins+docker 微服务实现自动化部署安装和部署过程
运维·数据库·git·docker·微服务·自动化·jenkins