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

背景概诉

刚开始接触领域驱动设计是在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

3

  • 识别每个活动的业务功能/能力
  • 确定此活动的用户角色(参与者)
  • 确保活动列表完成整个业务解决方案

  • 根据上述的核心活动,映射到界限上下文

  • 细化领域模型设计

到这里,相信很多同学都有了一个清晰的轮廓,但是仍有同学会问,从哪里开始呢?

不妨从一个简单的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鼓励业务人员、开发人员和其他利益相关者之间的密切合作和沟通,以确保软件系统的设计和实现能够准确地满足业务需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值