【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

阿里技术专家详解 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:084404

评论 1 条评论

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

分布式认知工业互联网

CECBC

架构实战营 模块一作业

面向对象的猫

「架构实战营」

疯了吧!这帮人居然用 Go 写“前端”?(一)

尔达Erda

开发者 云原生 大前端 后端 开发

GrowingIO Design 组件库搭建之Select组件

GrowingIO技术专栏

select 组件实现 virtualList

模块2作业

cherrycheek

k8s 节点机器重启 CNI IP 未回收

Geek_f24c45

Kubernetes cni

动手训练属于自己的无人车,这个超强服务现已开源!

亚马逊云科技 (Amazon Web Services)

视频云峰会|“超视频化时代的全景创新” 是什么?

阿里云视频云

阿里云 音视频 视频 视频解码 视频制作

架构实战营模块1课后作业

蔸蔸

5分钟速读之Rust权威指南(三十五)状态模式

wzx

rust

分布式磁盘 KV 存储 - Kvrocks

Kvrocks

RocksDB redis cluster Redis 协议 storage KV存储引擎

【LeetCode】从前序与中序遍历序列构造二叉树Java题解

Albert

算法 LeetCode 7月日更

双非小伙暑期实习斩获腾讯WXG offer,这不比博人传燃?

北游学Java

Java 腾讯 面试

必看!史上最全云原生全景图解读攻略来啦

尔达Erda

容器 微服务 运维 云原生 技术人

基于MaxCompute快速打通数据仓库和数据湖的湖仓一体实践

阿里云大数据AI技术

极光开发者周刊【No.0702】

极光JIGUANG

第一模块作业

晨晨

架构实战营

架构实战营模块一作业

王晓宇

架构实战营

排序——插入排序

若尘

数据结构 排序 排序算法 插入排序

《啊哈C语言!》学习笔记(1)

Nydia

垃圾回收器的前世今生

中原银行

垃圾回收

IPFS挖矿算力排行榜?IPFS挖矿排行榜?

别再往收藏夹怼资源了,收好这个视频创作导航网站就够了。

彭宏豪95

效率 视频 导航网站 视频制作

为什么你总是买了很多不需要的东西?

石云升

消费者 7月日更

辩证|北鲲云和你一起探讨关于生命科学发展的意义

北鲲云

每天学习10个实用Javascript代码片段(一)

devpoint

JavaScript LocalStorage 7月日更

秋招提前批开始了,一文带你了解,C++ 后台开发知识点及学习路线

奔着腾讯去

学习 程序员 面试 校园招聘 秋招

多维数据分析(OLAP)技术选型(1):对什么进行选型

水滴

OLAP 通信协议 技术选型 多维分析 存储格式

GitHub霸榜月余的24万字Java面试手册,竟是阿里机密

Java 面试

以太坊的 ChainId 与 NetworkId

杰哥的技术杂货铺

区块链 以太坊 ChainId NetworkId

2021最新一线大厂Java高级架构师面试题总结

Java 程序员 架构 面试

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