欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送!
在我后台回复 「资料」 可领取
编程高频电子书
!
在我后台回复「面试」可领取硬核面试笔记
!文章导读地址:点击查看文章导读!
感谢你的关注!
基于电商履约场景的 DDD 实战
第二部分:战术设计
名词介绍
第二部分来说一下战术设计都要做哪些事情
在战术设计中,牵扯到了具体的类层面的设计,涉及到每一个上下文里有哪些类,类之间如何配合
在战术设计中,包含了:
-
聚合(Aggregate):将多个联系很强的类聚合在一起,聚合后的东西就是一个聚合,下边的 Order 就是一个聚合
public class Order { // 唯一表示 private orderId; // 多个订单细节 List<OrderItem> orderItems; // 发货地址 DeliveryAddress deliveryAddress; }
-
实体(Entity):实体比较好理解,就是实体类,比如对于订单来说,它的实体就是 Order 类,需要唯一标识
-
值对象(Value Object):值对象是一种特定的实体,它代表了一个不可变的、没有唯一标识的对象。就比如在电商系统中,Money 是一个值对象,当存在多个 Money 对象时,只要他们的货币类型和金额相同,那它们就是相同的,值对象中通过属性值就可以判断是不是相同的,因此不再需要唯一标识
-
仓储(Repository):将聚合数据、实体数据和持久层进行持久化的组件,通过仓储进行数据的查询、修改、保存等操作,包括和 MySQL、Redis、ES、MQ 进行交互
-
领域服务(Domain Service):用于存放无法单独放在某个聚合中的一些业务逻辑
-
业务组件(Business Component):业务组件就是一段业务逻辑,既不是聚合,也不是领域服务,就比如订单状态机,表示订单的不同状态,这就是一个业务组件
// 订单状态及业务组件 public class OrderStateMachine {}
-
领域事件(Domain Event):通过领域事件来串联起整个业务,就比如通过订单支付事件(OrderPayedEvent)驱动发起履约的流程,订单上下文发布【订单支付事件】,履约上下文订阅【订单支付事件】,监听到事件发生后,就开始执行履约的流程
-
命令(Command):由人驱动发起的命令,例如点击按钮、提交表单等等,这些都是命令
贫血模型和充血模型
这里先说一下贫血模型和充血模型的概念
- 贫血模型
贫血模型就是我们之前使用 MVC 架构写出来的实体类的 class,这些 class 一般都是贫血模型
贫血指的是这些 class 中只存储一些数据,和数据库表中的字段是一一对应的,而对这些数据的一些业务逻辑操作,全部放在了其他地方(Service)
那么之前我们用 MVC 架构写出来的东西,业务逻辑全部封装在 Service 中,Domain 中的实体类中只有数据,没有业务逻辑,因此说 Service 是充血模型,Domain 是贫血模型
Service 中包装了太多的业务逻辑所导致的问题就是 Service 中的代码太多,导致代码看起来非常混乱
- 充血模型
在 DDD 中,是 将聚合设计成充血模型
了,聚合其实就是一堆实体类的集合,将业务行为放在聚合中去,将业务逻辑从 Service 中提取出来,按照业务语义放到对应的聚合中去,比如说对于 Order 订单来说,保存订单的操作,可以放在 Order 这个聚合中去:
public class Order {
// 唯一表示
private orderId;
// 多个订单细节
List<OrderItem> orderItems;
// 发货地址
DeliveryAddress deliveryAddress;
// 业务行为
public void saveOrder(/*省略*/) {/*省略*/}
}
领域服务有什么用?
这里讲一下领域服务到底是干什么的:
上边讲了将一些业务逻辑给抽取到聚合中去,但是还有一些业务逻辑可能设计了多个聚合,并不只属于某一个聚合,而是作为一个业务组件存在的,因此不能抽取到某一个聚合中
那么可以再去设计一个领域服务 DomainService,将业务逻辑放到领域服务中去
如何验证你的 DDD 架构设计的好不好呢?
有一个很简单的方法就是,让产品经理或者不懂代码的人,来看你代码的流程,如果代码可以很清晰的还原出来业务语义,那么就证明你的 DDD 架构设计的非常好了!
履约上下文业务流程梳理
这里再来梳理一下履约中的业务流程,之后就开始对履约上下文进行战术设计了
- 接收订单:通过监听【订单支付成功】事件
- 保存订单:将订单相关数据保存在履约的上下文中
- 预分仓:先对订单分配一个距离比较近的仓库
- 风控拦截:通过风控上下文提供的接口,拦截有风险的请求
- 人工审核:将拦截到的风险请求给人工进行审核
- 审核通过、重分仓、取消订单:人工审核后,对订单状态进行更新
- 分物流:通过物流上下文提供的接口,分配一个合适的物流公司
- 下库房:通过仓储上下文提供的接口,将履约订单指派给指定的仓库
DDD 中的接口层和应用服务层
接口层, 是 DDD 最靠外的一个层次,用于向外部提供 Http 调用
而像 MQ 中的消费者,也算是接口层,是基于 MQ 事件消费的接口
当接口层收到外部了 Http 请求之后,可以通过防腐层做一些数据的适配,将外部数据转为内部数据,之后再去执行对应的一系列的业务逻辑
应用服务层,用于进行业务流程编排,就比如由 ApplicationService 来进行履约整个流程的编排,负责基于仓储、聚合、领域服务来执行各种各样的动作,完成整个业务流程中所需要执行的动作
DDD 战术设计细节
我将履约流程中的战术设计给画了出来,其实 DDD 业务建模中,最重要的就是战术设计
通过战术设计,基本上就将你负责的上下文的模型给建立好,通过战术建模就可以看出来
接下来对上边的这张战术设计的流程图介绍一下**(圆圈内的都是履约上下文)**:
- 首先订单上下文发布【订单支付成功】的事件,被履约上下文的监听器监听到,进入到
接口层
,也就是履约上下文的入口 - 履约上下文的
应用服务层
负责整个履约链路的执行 - 首先是
保存履约单
,这里就涉及到数据的持久化,交给仓储
来做,其中履约单作为一个聚合
包含了多个实体,以及一些业务逻辑
(DDD 精华:将聚合设计为充血模型) - 接下来就是风控拦截,调用风控上下文提供的 API,与风控上下文使用 conformist 映射关系,完全遵守风控的接口规范(因为不处于一个团队,风控团队按照自己的计划迭代接口)
- 接下来是预分仓和分物流,这里创建了一个履约的领域服务,因为这里边的业务逻辑没有办法单独放到某个聚合中去,将预分仓和分物流这两个逻辑抽取到领域服务中
- 预分仓:创建一个 Warehouses 仓库集合的业务组件和 Warehouse 仓库的业务组件,通过这两个业务组件执行一些仓库相关的业务操作,通过调用仓储上下文的 API 接口拿到仓库集合,并且挑选距离最近的仓库,之后再检查选出来的仓库库存是否充足,并锁定库存(调用仓储上下文的 API 接口)
- 分物流:和预分仓类似,这里就不重复说了
那么至此,履约的战术设计就已经完成了