从混乱到优雅:基于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 转载请注明来源
从混乱到优雅:基于DDD的六边形架构的代码翻新指南的更多相关文章
- OnionArch 2.0 - 基于DDD的洋葱架构改进版开源
大家好,去年我发布了一篇 OnionArch - 采用DDD+CQRS+.Net 7.0实现的洋葱架构.很多程序员都比较感兴趣,给我要源代码.这次我把OnionArch进行了升级,改进了一些特性,并放 ...
- 基于DDD的微服务设计和开发实战
你是否还在为微服务应该拆多小而争论不休?到底如何才能设计出收放自如的微服务?怎样才能保证业务领域模型与代码模型的一致性?或许本文能帮你找到答案. 本文是基于 DDD 的微服务设计和开发实战篇,通过借鉴 ...
- 基于DDD的现代ASP.NET开发框架--ABP系列之3、ABP分层架构
基于DDD的现代ASP.NET开发框架--ABP系列之3.ABP分层架构 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ABP的官方网站:ht ...
- 基于DDD的现代ASP.NET开发框架--ABP系列之2、ABP入门教程
基于DDD的现代ASP.NET开发框架--ABP系列之2.ABP入门教程 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boi ...
- 线上分享-- 基于DDD的.NET开发框架-ABP介绍
前言 为了能够帮助.Net开发者开拓视野,更好的把最新的技术应用到工作中,我在3月底受邀到如鹏网.net训练营直播间为各位学弟学妹们进行ABP框架的直播分享.同时为了让更多的.NET开发者了解ABP框 ...
- 基于DDD的现代ASP.NET开发框架--ABP系列之1、ABP总体介绍
点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之1.ABP总体介绍 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)” ...
- ABP 基于DDD的.NET开发框架 学习(一)
ABP总体介绍 ABP是ASP.NET Boilerplate Project,ASP.NET样板项目. ABP框架定位于快速开发 ABP是一个用于最快实践和流行开发现代Web应用程序的新起点,旨在成 ...
- 3.3 Execution Flow of a DDD Based Application 基于DDD的应用程序执行流程
3.3 Execution Flow of a DDD Based Application 基于DDD的应用程序执行流程 The figure below shows a typical reques ...
- 基于DDD+微服务的开发实战(1)
1 DDD是什么? DDD是领域驱动设计,是Eric Evans于2003年提出的,离现在有17年. 2 为什么需要DDD 当软件越来越复杂,实际开发中,大量的业务逻辑堆积在一个巨型类中的例子屡见不鲜 ...
- IDDD 实现领域驱动设计-SOA、REST 和六边形架构
上一篇:<IDDD 实现领域驱动设计-架构之经典分层> 阅读目录: SOA-面向服务架构 REST 与 RESTful 资源(Resources) 状态(State) 六边形架构 DDD ...
随机推荐
- python3使用ESL和sipp自动多轮压测FreeSWITCH
环境:CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 sipp版本:3.6.1 python版本:3.9.12 日常工作中,有时会遇到批量自动压测FreeSWITC ...
- 青少年CTF平台-Web-Flag在哪里
平台名称:青少年CTF训练平台 题目名称:Flag在哪里? 解题过程: 启动环境,需要等待大概20秒左右的时间. 访问,页面显示Flag反正不在这. 右键网页,发现无法使用右键. 那么我们直接F12 ...
- 最全linux基础知识
linux基础知识 [root@localhost ~]# 各位置表示什么意识 root:表示用户名 (现在的用户是root切换为test便是张三) localhost:表示主机名 (当前主机名切换为 ...
- html5 2.0学习
列表定义:是一种特别的对象集合.集合:集中在一起合二为一(聚集). 聚集:多个列(信息资源)排在一起.信息资源:一堆数据,可能是字符,可能是图片. 列表分类:有序列表 无序列表 (自)定义列表 有 ...
- 干了这么多年C#,后悔没早点用这种“分页”,简单/高效/易维护
[前言] 干了这么多年C#,后悔没早点用这种"分页",简单/高效/易维护,比其它的分页方式强多了,不信你自己看. [正文] 支持.Net Core(2.0及以上)与.Net Fra ...
- Jmeter MD5加密及其运用
常用的几种加密方式 内置函数__MD5加密 参数说明: String to calculate MD5 hash(必填):要加密的字符串 Name of variable in which to st ...
- 【译】在 Visual Studio 中处理图像变得更容易了
任何 Web.桌面或移动开发人员都经常使用图像.你可以从 C#.HTML.XAML.CSS.C++.TypeScript 甚至代码注释中引用它们.有些图像是本地的,有些存在于线上或网络共享中,而其他图 ...
- 算术逻辑单元的实现(ALU)
一.实验目的 掌握Vivado集成开发环境 掌握Verilog语言基本知识. 掌握并理解算术逻辑单元ALU的原理和设计 二.实验预习 1.ALU(算术逻辑单元)的16种运算的编码 三.模块接口设计 A ...
- 2022最新 Navicat Premium 16中文软件激活安装永久使用正版(支持MAC+win)
Navicat Premium 16中文正版永久使用,下载地址: 关注我的wx公众号"奋斗在IT"回复1015获取下载地址
- 图解 LeetCode 算法汇总——链表
本文首发公众号:小码A梦 一般数据主要存储的形式主要有两种,一种是数组,一种是链表.数组是用来存储固定大小的同类型元素,存储在内存中是一片连续的空间.而链表就不同于数组.链表中的元素不是存储在内存中可 ...