解密DDD:高内聚对象组的维护之道

JPA 与 DDD 的==聚合写== 是绝配,但在 “读” 场景 往往会引发各种性能问题 。这也是很多公司弃用 JPA 而选择 MyBatis 的主要原因,就其本质并不是框架的错,而是将框架用在了错误的场景 。1. 初始 Repository

在 DDD 中,Repository 是一个非常重要的概念,它是领域层的一个组件,用来管理聚合根的生命周期和持久化 。
1.1. 核心为状态管理DDD 是由领域对象承载业务逻辑,所有的业务操作均在模型对象上完成,同一聚合上不同的业务操作构成了聚合的生命周期 。
我们以订单为例,如下图所示:
解密DDD:高内聚对象组的维护之道

文章插图
  1. 首先,用户操作下单,使用提交数据为其创建一个 Order 对象,版本 V1;
  2. 随后,用户进行改地址操作,调用 Order 对象的 modifyAddress 方法,Order 从原来的 V1 变成 V2;
  3. 用户完成支付后,调用 Order 对象的 paySuccess 方法,Order 从 V2 变成 V3;
整个流程很好的体现了 Order 聚合根的生命周期 。
1.2. 为什么需要 Repository?假设,有一台非常牛逼的计算机,计算资源无限、内存大小无限、永不掉电、永不宕机,那最简单高效的方式便是将模型对象全部放在内存中 。
但,现实不存在这样的机器,我们不得不将内存对象写入磁盘,下次使用时,在将其从磁盘读入到内存 。
整体结构如下图所示:
解密DDD:高内聚对象组的维护之道

文章插图
和上图相比,具有如下特点:
  1. 业务操作没变,仍旧依次完成 下单、改地址、支付等操作
  2. 引入持久化存储(MySQL),可以将 Order 对象存储于关系数据库
  3. 配合 Order 的生命周期,操作中增加 save、load 和 update 等操作
  1. 用户下单创建 Order 对象,通过 save 方法将 Order 对象持久化到 DB
  2. 接收到业务操作,需执行load,从 DB 加载数据到内存 并对 Order 对象的状态进行恢复
  3. 在业务操作完成后,需执行update,将 Order 对象的最新状态同步的 DB
相对全内存版本确实增加了不小的复杂性,为了更好的对这些复杂性进行管理,引入 Repository 模式 。
在领域驱动设计(DDD)中,Repository 是一种设计模式,它是用来存储领域对象的容器 。它提供了一种统一的方式来查询和存储领域对象 。Repository提供了对底层数据存储的抽象,允许应用程序在没有直接与数据存储技术交互的情况下访问数据,同时该抽象允许在不修改应用程序代码的情况下更改数据存储技术 。
【注】在 DDD 中,Repository 并不是一个 DAO,它的职责比 DAO 要多得多,它管理的是整个聚合根,而不是单个实体对象 。同时,Repository 还需要提供一些查询接口,用来查询聚合根的状态 。
2. 什么是好的 Repository?Repository 主要用于完成对聚合根生命周期的管理,所以必须提供三组操作:
  1. 保存 。将聚合根同步到底层存储进行持久化处理;
  2. 查询 。根据 ID 或属性从底层存储引擎中读取数据并恢复为内存对象,也就是聚合根对象;
  3. 更新 。聚合对象发生变更后,可以将新的状态同步到存储引擎,以便完成数据更新;
有人会说,这和 DAO 没啥区别吧!!!
DAO 是单表单实体操作,Repository 操作的是整个聚合甚至包括继承关系,这就是最大的区别 。也就是Repository 必须能够:
  1. 维护一个完整的对象组,也就是必须能处理对象的组合关系;
  2. 维护一个完整的继承体系,也就是必须能够处理对象继承关系;
既支持组合又支持继承,DAO 就没办法更好的承载了 。
那就完了吗?并没有!!!
聚合根是一个对象组,包含各种关系,并不是每个业务操作都需要聚合根内的所有实体 。
举个例子,在电商订单聚合根内,包括:
  1. 订单(Order) 。记录用户的一次生单,主要保存用户、支付金额、订单状态等;
  2. 订单项(OrderItem) 。购买的单个商品,主要保存商品单价、售价、应付金额等;


    推荐阅读