从混乱到优雅:基于DDD的六边形架构的代码翻新指南 | 京东物流技术团队
前言
趁着双十一备战封板,终于又有一些时间可以梳理一下最近的心得。
最近这半年跟同事讨论比较多的是分层架构,然后就会遇到两个触及灵魂的问题,一个是如何做好分层架构,二是DDD在架构层面该如何落地。
为了说好分层,我们需要了解架构的意义。
良好的架构是为了保证一下两点:
- 治理应用复杂度,降低系统熵值;
- 从随心所欲的混乱状态,走向井井有条的有序状态。
比如,你去图书馆借阅书籍,对于纷繁杂乱的各类书籍,如果不能很好的管理和分类,必然会导致图书馆管理混乱,效率低下,使得图书馆不能正常运维。而分层架构的意义也在于此,当我们面对复杂的业务需求时,需要更好的规划我们的包结构和依赖规约,可以更好的治理我们的服务,提升服务的可维护性,可扩展性,做到我们的架构以业务为核心,解耦外部依赖,分离业务复杂度和技术复杂度。
传统分层架构有MVC,而这些年流行的六边形架构,也是伴随着DDD的兴起而逐步被大家所接受。如果说DDD和六边形架构的关系,他俩属于不同层级的概念,DDD更偏向方法论,注重领域建模和业务逻辑的设计,强调将业务需求和领域知识转化为软件设计;而六边形架构更注重系统的整体架构和模块化设计,强调分离内部和外部系统的交互。他们俩的结合是一种非常好的实践经验, DDD中的领域模型是核心,其他层(如应用层、基础设施层)依赖于领域模型;而六边形架构正好为DDD提供了一种非常好的分层落地。
浅聊DDD落地
对于DDD,并没有一种所谓的框架或者脚手架能够对应,其根本原因在于,DDD其实是一种方法论,而非所谓的框架,它给我们提供了一种应对业务复杂度时的方法:
- 通过架构设计来分离业务复杂度和技术复杂度;
- 通过限界上下文去做到分而治之,将大系统拆解为若干个高内聚低耦合的子域;
- 通过面向对象的设计模式,将业务子域的知识进行抽象。
总结一下:
- DDD的战略建模注重子域的划分和限界上下文的定义。对应到落地就是包的拆解, 以及包之间的依赖和组合关系。
- 而DDD的战术建模主要关注的是构造块和柔性设计。构造块就是我们常说的,类,对象,组合。而柔性设计就是我们面向对象的设计原则,得到一个高内聚低耦合的系统。所以说,DDD的战术建模落地,一定伴随着开发人员对设计模式的深刻理解和应用。
六边形分层架构
1. App层
应用层是DDD中的顶层,负责协调和组织领域对象的交互。它接收来自用户界面或外部系统的请求,并将其转发给领域层进行处理。应用层负责定义应用的用例(Use Cases),处理事务边界和协调领域对象的操作。它不包含业务逻辑,而是将请求转化为领域对象的操作。应用层还可以包含获取输入,组装上下文,参数校验,异常定义,发送事件通知等。
2. Domain层
主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(Domain Entity)的方法对App层提供业务实体和业务逻辑计算。领域是应用的核心,不依赖任何其他层次。同时领域层会有一个facade层,当领域服务对外部有调用依赖时,通过定义facade接口实现控制反转。
3. Adapter层
负责与外部系统进行或者服务进行适配和集成,包括通信,数据缓存,接口适配等功能。
此外强调, RPC consumer调用放在适配器层。适配器层专注于与外部系统的集成和适配,将外部系统的接口和数据格式转换为应用程序可以理解和处理的形式。将RPC调用放在适配器层可以更好地将与外部系统相关的技术细节与应用程序的业务逻辑和领域对象进行解耦,提高应用程序的可扩展性和可维护性。
对于所有出站适配层,都需要通过实现facade接口实现控制反转。
4. 基础设施层
负责提供支持应用程序运行的基础设施,包括与具体技术相关的实现。基础设施层通常包括与数据库、消息队列、缓存、外部服务等进行交互的代码,以及一些通用的工具类和配置,也包括filter等实现。
基础设施层和适配器层之间的关系是:
- 基础设施层提供了与具体技术相关的实现,例如数据库访问、消息队列连接、缓存操作等。适配器层可以使用基础设施层提供的功能来与外部系统进行交互。
- 适配器层通过适配器模式或类似的机制,将外部系统的接口和数据格式转换为应用程序可以理解和处理的形式。适配器层还负责将应用程序的请求转发给基础设施层进行具体的操作。
- 基础设施层和适配器层一起工作,使得应用程序能够与外部系统进行集成,并且将与外部系统相关的技术细节与应用程序的业务逻辑和领域对象进行解耦。这样可以实现应用程序的可扩展性、可维护性和可测试性。
对于一些无复杂逻辑的,也可以直接让上游掉基础设施层,不必一定通过Adapter层。
脚手架的落地实践
以上主要是理论介绍,基于以上的说明,在实践中,我搭建了两套分层架构的java脚手架。具体来说分为单module版本和多module版本。对于微服务系统来说,如果你的每个服务业务复杂度不高,建议使用单module版本;如果你是个复杂业务场景的单体应用,建议采用多module版本。
1. 单module脚手架
--root --application: 应用层是程序的入口,整合和组合domain提供的能力。 --rpc: JSF provider对外提供的接口实现 --controller: springMVC提供的controller --listener: MQ消息监听器 --task: 调度任务 --translate: 将内部的BO映射为外部的VO/Entity --model: VO对象 --adapter: 适配器层 --rpc: JSF consumer,外部服务 --mq: 消息队列sender模块 --translate: 将外部数据结构映射为内部的DTO/BO --domain: 领域层 --service: 领域服务可以按照自己情况灵活设计 --facotry: 工厂 --event/command: 事件驱动 --model: 对象和实体 --translate: 对象实体映射转换 --infrastructure: --repository: 持久化层,包括db模型,sql读写等 --cache: Redis缓存读写 --producer: MQ消息生成,即发送MQ消息。 --config: 配置信息,例如ducc配置、数据库、缓存配置等 --translate: 将存储层的数据结构PO映射为内部的BO --utils: 工具集合 --common: 公共层 --exception: 主要分为业务异常和系统异常。系统异常需要研发处理。业务异常需要具备监控能力。 --utils: 工具类 --enums: 枚举类 --common: 全局公共常量池 --worker: 异步服务 --client: JSF SDK
maven私服拉取脚本如下:
单module版本maven私服拉取脚本如下:
mvn archetype:generate \ -DarchetypeGroupId=com.jd.magnus \ -DarchetypeArtifactId=magnus-single-archetype \ -DarchetypeVersion=1.0.0-SNAPSHOT \ -DinteractiveMode=false \ -DarchetypeCatalog=remote \ -Dversion=1.0.0-SNAPSHOT \ -DgroupId=com.jdl.sps \ -DartifactId=bff-single-demo1
2. 多module脚手架
此处有一个建议,在多module版本下,因为是复杂单体应用,所以建议内部进行拆包处理。每层内也可以基于不同领域场景也可以进行拆包操作,每个场景下层级结构是一样的。如下图举例,其中app层中分别有两个业务场景,包括商品和订单:
多module版本的maven私服拉取脚本如下:
mvn archetype:generate \ -DarchetypeGroupId=com.jd.magnus \ -DarchetypeArtifactId=magnus-multi-ddd-archetype \ -DarchetypeVersion=1.0.0-SNAPSHOT \ -DinteractiveMode=false \ -DarchetypeCatalog=remote \ -Dversion=1.0.0-SNAPSHOT \ -DgroupId=com.jdl.sps \ -DartifactId=bff-demo1
小结
本框架是结合了DDD思想和六边形架构思想,但脚手架不会限制大家能力和发挥。
如果你精通DDD,你可以在domain层采用标准的充血模型和子域拆分模式编写你的代码; 如果你精通MVC,该框架也可以简化为大家熟悉的MVC开发模式。对于model的处理,也可灵活应对,在不影响整体代码架构的情况下,允许不过度设计及对象多度封装,鼓励敏捷迭代和定期重构。
但有一个核心思想需要谨记:
我们尽量保证我们的代码开发符合开闭原则,能够通过增加类和方法的方式实现新功能迭代,尽量就要避免频繁修改某个方法或者某个类,包与包之间要保证高内聚,低耦合。因为DDD思想的核心就是子域的拆分和对设计模式的合理运用。
作者:京东物流 赵勇萍
来源:京东云开发者社区 自猿其说 Tech 转载请注明来源

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
怎样阅读 h2 数据库源码 | 京东物流技术团队
阅读 h2 数据库的源码是一项复杂的任务,需要对数据库原理、Java 语言和操作系统有深入的理解。可以从以下几方面入手来完成。 环境准备 首先,你需要在你的机器上安装和配置好开发环境,包括 JDK、Maven、IDE 调试器等工具。 然后,从h2 的官方网站或GitHub上下载源码。 IDE 导入 h2 数据库源码,根据不同的调试场景,启用不同的模式。 Client/Server 模式 # 约等于 java -cp h2-*.jar org.h2.tools.Console java -cp h2-*.jar 本地 Shell 模式 java -cp h2-*.jar org.h2.tools.Shell 理解架构 在阅读源码之前,理解 h2 数据库的整体架构和主要组件是非常重要的。可以从官方文档或在线教程中获取这些信息。 官方架构讲解Architecture 选择关注点 h2 数据库的源码非常多,功能非常丰富,可能无法一次性完全理解。因此,选择一个特定的模块或功能(如查询优化器、存储引擎、事务处理等)作为起点,然后逐步扩大你的阅读范围。 基于的 BTree Pa...
- 下一篇
Java表达式引擎选型调研分析 | 京东云技术团队
1 简介 我们项目组主要负责面向企业客户的业务系统,企业的需求往往是多样化且复杂的,对接不同企业时会有不同的定制化的业务模型和流程。我们在业务系统中使用表达式引擎,集中配置管理业务规则,并实现实时决策和计算,可以提高系统的灵活性和响应能力,从而更好地满足业务的需求。 举个简单的例子,假设我们有一个业务场景,在返利系统中,当推广员满足一定的奖励条件时,就会给其对应的奖励金额。例如某个产品的具体奖励规则如下: 奖励条件 奖励金额 拉新用户数大于等于3个且客单价大于50元 100元 拉新用户数大于等于5个且客单价大于100元 200元 拉新用户数大于等于10个且客单价大于200元 500元 这个规则看起来很好实现,只要在代码里写几个if else分支就可以了。但是如果返利系统对接了多家供应商,且每家提供的产品的奖励规则都不同呢?再通过硬编码的方式写if else似乎就不太好了,每次增加修改删除规则都需要系统发版上线。 引入规则引擎似乎就能解决这个问题,规则引擎的一个好处就是可以使业务规则和业务代码分离,从而降低维护难度,同时它还可以满足业务人员通过编写DSL或通过界面指定规则的诉求,这样就可...
相关文章
文章评论
共有0条评论来说两句吧...