趁着双十一备战封板,终于又有一些时间可以梳理一下最近的心得。
最近这半年跟同事讨论比较多的是分层架构,然后就会遇到两个触及灵魂的问题,一个是如何做好分层架构,二是 DDD 在架构层面该如何落地。
为了说好分层,我们需要了解架构的意义。
良好的架构是为了保证一下两点:
比如,你去图书馆借阅书籍,对于纷繁杂乱的各类书籍,如果不能很好的管理和分类,必然会导致图书馆管理混乱,效率低下,使得图书馆不能正常运维。而分层架构的意义也在于此,当我们面对复杂的业务需求时,需要更好的规划我们的包结构和依赖规约,可以更好的治理我们的服务,提升服务的可维护性,可扩展性,做到我们的架构以业务为核心,解耦外部依赖,分离业务复杂度和技术复杂度。
传统分层架构有 MVC,而这些年流行的六边形架构,也是伴随着 DDD 的兴起而逐步被大家所接受。如果说 DDD 和六边形架构的关系,他俩属于不同层级的概念,DDD 更偏向方法论,注重领域建模和业务逻辑的设计,强调将业务需求和领域知识转化为软件设计;而六边形架构更注重系统的整体架构和模块化设计,强调分离内部和外部系统的交互。他们俩的结合是一种非常好的实践经验, DDD 中的领域模型是核心,其他层(如应用层、基础设施层)依赖于领域模型;而六边形架构正好为 DDD 提供了一种非常好的分层落地。
对于 DDD,并没有一种所谓的框架或者脚手架能够对应,其根本原因在于,DDD 其实是一种方法论,而非所谓的框架,它给我们提供了一种应对业务复杂度时的方法:
总结一下:
应用层是 DDD 中的顶层,负责协调和组织领域对象的交互。它接收来自用户界面或外部系统的请求,并将其转发给领域层进行处理。应用层负责定义应用的用例(Use Cases),处理事务边界和协调领域对象的操作。它不包含业务逻辑,而是将请求转化为领域对象的操作。应用层还可以包含获取输入,组装上下文,参数校验,异常定义,发送事件通知等。
主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(Domain Entity)的方法对 App 层提供业务实体和业务逻辑计算。领域是应用的核心,不依赖任何其他层次。同时领域层会有一个 facade 层,当领域服务对外部有调用依赖时,通过定义 facade 接口实现控制反转。
负责与外部系统进行或者服务进行适配和集成,包括通信,数据缓存,接口适配等功能。
此外强调, RPC consumer 调用放在适配器层。适配器层专注于与外部系统的集成和适配,将外部系统的接口和数据格式转换为应用程序可以理解和处理的形式。将 RPC 调用放在适配器层可以更好地将与外部系统相关的技术细节与应用程序的业务逻辑和领域对象进行解耦,提高应用程序的可扩展性和可维护性。
对于所有出站适配层,都需要通过实现 facade 接口实现控制反转。
负责提供支持应用程序运行的基础设施,包括与具体技术相关的实现。基础设施层通常包括与数据库、消息队列、缓存、外部服务等进行交互的代码,以及一些通用的工具类和配置,也包括 filter 等实现。
基础设施层和适配器层之间的关系是:
对于一些无复杂逻辑的,也可以直接让上游掉基础设施层,不必一定通过 Adapter 层。
以上主要是理论介绍,基于以上的说明,在实践中,我搭建了两套分层架构的 java 脚手架。具体来说分为单 module 版本和多 module 版本。对于微服务系统来说,如果你的每个服务业务复杂度不高,建议使用单 module 版本;如果你是个复杂业务场景的单体应用,建议采用多 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
此处有一个建议,在多 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 转载请注明来源