DDD学习笔记
本文是博客学习笔记,可以说是缝合怪,原文在后面的参考链接中
什么是 DDD
与传统架构的差异
常见的业务三层架构:
再看 DDD 的架构:
来看两者的区别:
- DDD 架构中,分为了接口层,应用层,领域层,基础设施层。相比于原来的三层模型,可以认为 DDD 讲业务层拆分为了应用层和领域层。其中,应用层主要是做业务逻辑的组装,它会调用基础设施和领域层的接口来组装业务。而领域层则是一些核心的业务操作,最关键的是,它是纯内存操作。于是我们可以这样理解,DDD 将一些核心业务的操作从业务层剥离出来了!
- UI 层和接口层是类似的
- 基础设施层包含了一切和外部应用交互的接口。可以说是包含了原来数据访问层的内容
理论解释
领域驱动设计(domain driven design)。首次出现在十几年前,却在微服务时代大放光彩。 DDD 是作为一种战略思想可用于指导微服务的拆分,DDD 本质不是一个软件架构, 而是一种指导思想。它涉及的术语很多,简单介绍几个我印象比较深刻的。
- 领域对象(Entity):最重要的概念模型,可以理解和数据表有一种映射关系。然而又不会和数据表直接交互。它完全是内存操作,不会有外部依赖(ps:理解这一点很重要,这是整个 DDD 能做到高内聚的核心)
- 值对象(Value Object):相比于 Entity,它不需要和数据表有映射关系,可以理解为一种更为基础的概念模型,同样的,它也完全内存操作,没有外部依赖
- 领域服务:需要跨多个领域对象的业务操作,需要用领域服务来完成
- 聚合根(Aggregate):对于那种比较复杂的业务,单一 Entity 无法完全表示它的含义,就需要多个 Entity 共同表示,就放在了一个聚合根中
- 业务域:指的是具体的一块业务,比如订单业务域,报价业务域之类的
- 充血模式:相比于平时写的比较多的只有 set/get 方法的 pojo 类,它还包含了一些行为方法,用于修改自身的状态值
只要记住,它主要分为战略设计和战术设计。
- 战略设计:划分业务域。这里的划分和技术实现完全没有关系,只是业务上的划分。需要业务专家来做决断,这里做的好不好严重影响整个系统架构的质量。(ps:对于架构师来说,业务能力和技术能力一样重要)
- 战术设计:具体的设计出一个 Entity,Value Object,Aggregate 等内容
DDD 有什么优点
一个 bad case 的改造
一个简单的案例需求如下:
用户可以通过银行网页转账给另一个账号,支持跨币种转账。
同时因为监管和对账需求,需要记录本次转账活动。
拿到这个需求之后,一个开发可能会经历一些技术选型,最终可能拆解需求如下:
1、从 MySql 数据库中找到转出和转入的账户,选择用 MyBatis 的 mapper 实现 DAO;
2、从 Yahoo(或其他渠道)提供的汇率服务获取转账的汇率信息(底层是 http 开放接口);
3、计算需要转出的金额,确保账户有足够余额,并且没超出每日转账上限;
4、实现转入和转出操作,扣除手续费,保存数据库;
5、发送 Kafka 审计消息,以便审计和对账用;
传统的写法会写出如下代码:
1 | |
其实从可读性来说,上面的代码是没有什么问题的,注释也很详细。是一段很传统的业务层代码。
但是,我们可以看到这一段业务代码中包含了参数校验,数据读写存储,业务计算,调用外部服务,发送消息等操作。这种形式的代码,我们一般叫做“胶水式代码”,有点面向过程编程的意味了。
整个代码的调用链路:
这是按照原来传统三层架构拆解的。
“高内聚,低耦合”是对一个好的架构的评价的核心标准。如果对整体架构进行重构的话,就是降低各层之间的依赖。降低依赖的核心解决方案,就是再加一层抽象层!
抽象存储层
上面的结构是直接依赖了 mybatis+mysql 的实现,后面如果想更改数据源也很不方便。所以加个抽象层隔离开具体实现。具体做法是:
- 新建 Account 实体对象:一个实体(Entity)是拥有 ID 的域对象,除了拥有数据之外,同时拥有行为。Entity 和数据库储存格式无关,因此是需要一个 DO 对象来和数据库映射的
- 新建对象储存接口类 AccountRepository:Repository 只负责 Entity 对象的存储和读取,而 Repository 的实现类完成数据库存储的细节。通过加入 Repository 接口,底层的数据库连接可以通过不同的实现类而替换。
更改后的架构:
抽象第三方服务
这里的第三方服务是指“YahooForexService”。虽然这个已经是一个接口了,但是这里其实调用的还是服务提供者的具体实现。如果要隔离服务未来发生变化的可能性,还是要加一层抽象层。这种一般会被称为“防腐层”。有了防腐层,外部逻辑再怎么变化,内部都可以尽可能不变!
除了隔离变化,防腐层还一般有如下功能:
- 适配器:基本功能,将外部数据转为内部数据对象
- 缓存:很适合在这里做调用的缓存层
- 监控日志:方便后面快速定位是外部系统还是内部系统的问题
- 快速降级:这里在这里做兜底逻辑
添加后的架构:
抽象中间件
在对中间件的使用上加一层抽象,主要是中间件的返回值一般都是 byte[ ]结构,我们一般都会有序列化和反序列化的操作,这种操作是不属于业务逻辑的,不应该放在应用层。
封装业务逻辑
其实重构到现在,我们主要是把系统和外部数据交互的部分都加上了一层抽象层。剩余的就是一下业务规则计算了,这些是属于纯内存操作。而这些纯内存操作正是整个业务系统最核心的地方。也正是业务域的关键。
一般封装业务逻辑,我们第一步是将业务逻辑抽象概括出一些核心的 Entity 或者 Value Object。第二步是判断业务逻辑中有没有一些跨 Entity 的操作,如果有的话,就要新增“Domain Service”,用于处理多对象逻辑。
这里的话,就新增了 Account(Entity),ExchangeRate(Value Object) ,AccountTransferService (Domain Service)
最后就变成:
改造后效果
从代码上来看,其实区别不是很大:
1 | |
而它的业务分层则按照 DDD 的方式来看:
- 领域层纯粹是内存操作,不依赖任何外部依赖
- 应用层则依赖抽象,而不依赖实现,有效隔离了变化。而应用层做的核心工作就是拼装逻辑
- 其实对于 DDD 的四层架构来看,实际情况最容易发生变化的是领域层。业务规则最容易变化,而其他的基础设施层,接口层一般来说都不会做很大变动。这也符合领域驱动设计的思想。
应用到实际工作中
其实主要是有一些启发吧。DDD 本质是一种架构思想,为拆分微服务提供了指导方针。我阅读的博客大部分是一些偏向于能落地的,所以我对 DDD 的理解可能会有一些偏差。那几本著名的 DDD 的技术书都没有研究过。
不过,基于我目前的知识水平,还是能收获很多的。
- 业务开发的核心难点在于业务的复杂度,而业务的复杂度其实可以通过一些良好的架构设计来减少一些。后续的工作时,除了技术水平,业务水平也得一起练习。
- 高内聚,低耦合。这句话很重要,是架构设计的核心追求,我感觉我现在可能都理解的不够深刻。需要在平时的工作中多思考,如何做到这个
- 充血模型在一定场合可以用用,不过也不能说贫血模型就不合适
- DDD 不是瑞士军刀,不是每一个场景都需要像 DDD 这样,做这么多的抽象层的。对于一些工具类的需求,三层架构可能会带来更好的可读性。
参考链接
DDD领域驱动设计总结 - 知乎 (zhihu.com)
阿里技术专家详解DDD系列 第二弹 - 应用架构 (qq.com)