限时领|《AI 百问百答》专栏课+实体书(包邮)! 了解详情
写点什么

阿里技术专家详解 DDD 系列 第二弹 - 应用架构 (三)

  • 2019-12-18
  • 本文字数:2406 字

    阅读完需:约 8 分钟

阿里技术专家详解DDD系列 第二弹 - 应用架构(三)

3 DDD 的六边形架构

在我们传统的代码里,我们一般都很注重每个外部依赖的实现细节和规范,但是今天我们需要敢于抛弃掉原有的理念,重新审视代码结构。在上面重构的代码里,如果抛弃掉所有 Repository、ACL、Producer 等的具体实现细节,我们会发现每一个对外部的抽象类其实就是输入或输出,类似于计算机系统中的 I/O 节点。这个观点在 CQRS 架构中也同样适用,将所有接口分为 Command(输入)和 Query(输出)两种。除了 I/O 之外其他的内部逻辑,就是应用业务的核心逻辑。基于这个基础,Alistair Cockburn 在 2005 年提出了 Hexagonal Architecture(六边形架构),又被称之为 Ports and Adapters(端口和适配器架构)。



image.png


在这张图中:


  • I/O 的具体实现在模型的最外层

  • 每个 I/O 的适配器在灰色地带

  • 每个 Hex 的边是一个端口

  • Hex 的中央是应用的核心领域模型


在 Hex 中,架构的组织关系第一次变成了一个二维的内外关系,而不是传统一维的上下关系。同时在 Hex 架构中我们第一次发现 UI 层、DB 层、和各种中间件层实际上是没有本质上区别的,都只是数据的输入和输出,而不是在传统架构中的最上层和最下层。


除了 2005 年的 Hex 架构,2008 年 Jeffery Palermo 的 Onion Architecture(洋葱架构)和 2017 年 Robert Martin 的 Clean Architecture(干净架构),都是极为类似的思想。除了命名不一样、切入点不一样之外,其他的整体架构都是基于一个二维的内外关系。这也说明了基于 DDD 的架构最终的形态都是类似的。Herberto Graca 有一个很全面的图包含了绝大部分现实中的端口类,值得借鉴。



image.png


3.1 - 代码组织结构


为了有效的组织代码结构,避免下层代码依赖到上层实现的情况,在 Java 中我们可以通过 POM Module 和 POM 依赖来处理相互的关系。通过 Spring/SpringBoot 的容器来解决运行时动态注入具体实现的依赖的问题。一个简单的依赖关系图如下:




image.png


image.png

3.1.1 - Types 模块

Types 模块是保存可以对外暴露的 Domain Primitives 的地方。Domain Primitives 因为是无状态的逻辑,可以对外暴露,所以经常被包含在对外的 API 接口中,需要单独成为模块。Types 模块不依赖任何类库,纯 POJO 。



image.png

3.1.2 - Domain 模块

Domain 模块是核心业务逻辑的集中地,包含有状态的 Entity、领域服务 Domain Service、以及各种外部依赖的接口类(如 Repository、ACL、中间件等。Domain 模块仅依赖 Types 模块,也是纯 POJO 。



image.png

3.1.3 - Application 模块

Application 模块主要包含 Application Service 和一些相关的类。Application 模块依赖 Domain 模块。还是不依赖任何框架,纯 POJO。



image.png

3.1.4 - Infrastructure 模块

Infrastructure 模块包含了 Persistence、Messaging、External 等模块。比如:Persistence 模块包含数据库 DAO 的实现,包含 Data Object、ORM Mapper、Entity 到 DO 的转化类等。Persistence 模块要依赖具体的 ORM 类库,比如 MyBatis。如果需要用 Spring-Mybatis 提供的注解方案,则需要依赖 Spring。



image.png

3.1.5 - Web 模块

Web 模块包含 Controller 等相关代码。如果用 SpringMVC 则需要依赖 Spring。



image.png

3.1.6 - Start 模块

Start 模块是 SpringBoot 的启动类。

3.2 - 测试

Types,Domain 模块都属于无外部依赖的纯 POJO,基本上都可以 100%的被单元测试覆盖。


Application 模块的代码依赖外部抽象类,需要通过测试框架去 Mock 所有外部依赖,但仍然可以 100%被单元测试。


Infrastructure 的每个模块的代码相对独立,接口数量比较少,相对比较容易写单测。但是由于依赖了外部 I/O,速度上不可能很快,但好在模块的变动不会很频繁,属于一劳永逸。


Web 模块有两种测试方法:通过 Spring 的 MockMVC 测试,或者通过 HttpClient 调用接口测试。但是在测试时最好把 Controller 依赖的服务类都 Mock 掉。一般来说当你把 Controller 的逻辑都后置到 Application Service 中时,Controller 的逻辑变得极为简单,很容易 100%覆盖。


Start 模块:通常应用的集成测试写在 start 里。当其他模块的单元测试都能 100%覆盖后,集成测试用来验证整体链路的真实性。

3.3 - 代码的演进/变化速度

在传统架构中,代码从上到下的变化速度基本上是一致的,改个需求需要从接口、到业务逻辑、到数据库全量变更,而第三方变更可能会导致整个代码的重写。但是在 DDD 中不同模块的代码的演进速度是不一样的:


Domain 层属于核心业务逻辑,属于经常被修改的地方。比如:原来不需要扣手续费,现在需要了之类的。通过 Entity 能够解决基于单个对象的逻辑变更,通过 Domain Service 解决多个对象间的业务逻辑变更。


Application 层属于 Use Case(业务用例)。业务用例一般都是描述比较大方向的需求,接口相对稳定,特别是对外的接口一般不会频繁变更。添加业务用例可以通过新增 Application Service 或者新增接口实现功能的扩展。


Infrastructure 层属于最低频变更的。一般这个层的模块只有在外部依赖变更了之后才会跟着升级,而外部依赖的变更频率一般远低于业务逻辑的变更频率。


所以在 DDD 架构中,能明显看出越外层的代码越稳定,越内层的代码演进越快,真正体现了领域“驱动”的核心思想。

4 总结

DDD 不是一个什么特殊的架构,而是任何传统代码经过合理的重构之后最终一定会抵达的终点。DDD 的架构能够有效的解决传统架构中的问题:


高可维护性:当外部依赖变更时,内部代码只用变更跟外部对接的模块,其他业务逻辑不变。


高可扩展性:做新功能时,绝大部分的代码都能复用,仅需要增加核心业务逻辑即可。


高可测试性:每个拆分出来的模块都符合单一性原则,绝大部分不依赖框架,可以快速的单元测试,做到 100%覆盖。


代码结构清晰:通过 POM module 可以解决模块间的依赖关系, 所有外接模块都可以单独独立成 Jar 包被复用。当团队形成规范后,可以快速的定位到相关代码。


本文转载自淘系技术公众号。


原文链接:https://mp.weixin.qq.com/s/MU1rqpQ1aA1p7OtXqVVwxQ


2019-12-18 16:084815

评论 1 条评论

发布
用户头像
了不起的文章
2019-12-21 14:35
回复
没有更多了
发现更多内容

盘点2020|多线程的遥感大数据行业产品经历

老壳有点爽

大数据 盘点2020 InSAR

《O2O实战:他们是如何利用互联网的》.pdf

田维常

互联网

forsage以太坊矩阵系统软件开发|forsage以太坊矩阵APP开发

系统开发

flink 使用curl,通过RESTful api,上传和删除jar包

Geek_de9857

flink RESTful curl 上传jar 删除jar

Tufin联合Cisco,将网络安全自动化进行到底

Geek_448e23

网络安全 自动化管理 云安全

从阿里离职后人虚了,头也秃了,就剩这份Java性能优化的PDF了

Java架构追梦

Java 架构 面试 性能优化 并发

985研究生熬夜23天吃透845页架构宝典 终收割腾讯Java岗offer!再也不用怀疑人生了

比伯

Java 编程 架构 面试 程序人生

女朋友突然问我DNS是个啥....

乱敲代码

计算机网络 DNS DNS服务器

扩招1W人,字节跳动内部公开12月份Java岗71道面试题

比伯

Java 编程 架构 面试 程序人生

程序员因重复记录日志撑爆ELK被辞退!

Java架构师迁哥

盘点2020 | 一个三线程序员的2020年,不卑不亢

沉默王二

程序员 盘点2020 沉默王二

PostgreSQL高可用:多主复制解决方案

PostgreSQLChina

数据库 postgresql 开源

架构师训练营第 13 周学习总结

netspecial

极客大学架构师训练营

LeetCode题解:127. 单词接龙,双向BFS,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

Mysql中,1=1和 1=1=1 和 -1=-1 和 -1=-1=-1 和 5=5 和 5=5=5 有什么区别

Geek_de9857

MySQL sql 返回值 1=1=1 -1=1=1

架构师训练营第 1 期第 13 周作业

业哥

架构师训练营 1 期 -- 第十三周总结

曾彪彪

极客大学架构师训练营

从面试角度分析LinkedList源码

Java旅途

Java List 集合 linkedlist

等保数据备份和恢复关键点,这些你该知道!

华为云开发者联盟

数据 容灾 恢复

智能微卡口监控系统搭建,智慧平安小区建设解决方案

t13823115967

智慧城市

几款Java开发者必备常用的工具,准点下班不在话下

华为云开发者联盟

Java 工具 开发

DAPP软件系统开发

系统开发

阿里华为等大厂如何处理数值精度/舍入/溢出问题

Java架构师迁哥

OpenKruise v0.7.0 版本发布:新增周期任务分发控制器

阿里巴巴云原生

阿里云 容器 开发者 运维 云原生

建议收藏,mybatis插件原理详解

田维常

mybatis

干货丨如何使用Redash连接DolphinDB数据源

DolphinDB

数据处理 时序数据库 DolphinDB 数据库开发 redash

5张图搞懂Java引用拷贝、深拷贝、浅拷贝

bigsai

Java 浅拷贝和深拷贝

盘点2020 | 微服务架构下该如何技术选型呢?

xcbeyond

微服务 技术选型 盘点2020 选型原则

架构师 3 期 3 班 -week4- 作业

zbest

作业 week4

90分钟10个手写案例,从源码底层给你讲解7种线程池创建方式

996小迁

Java 源码 架构 资料 笔记

边缘计算和5G:我们从何而来?

VoltDB

云计算 大数据 5G 物联网 边缘计算

阿里技术专家详解DDD系列 第二弹 - 应用架构(三)_语言 & 开发_淘系技术_InfoQ精选文章