作者:京东物流 赵勇萍

前言

最近有空会跟同事讨论DDD架构的实践落地的情况,但真实情况是,实际中对于领域驱动设计中的实体,值对象,聚合根,领域事件这些战术类的实践落地,每个人理解依然因人而异,大概率是因为这些概念还是有一些抽象,同时有有别于传统的MVC架构开发。

在此,通过小demo的方式跟大家分享一下我对DDD中战术层级的理解,算是抛砖引玉,该理解仅代表我个人在现阶段的一个理解,也可能未来随着业务经验深入,还会有不同的理解。

既然说是小demo,还是要从业务场景出发,也就是我最熟知的电商业务场景说起。但是该篇文章里, 我会简化一些实际业务场景中的复杂度,通过最小颗粒度的demo,来反映实践过程中的基本问题。

一个简单的demo业务场景

话不多说,我先抛出我自己假设的一个业务场景,就是我们熟知的电商网站下单购物的场景。具体细节如下:

1. 实体:

• 商品:拥有唯一标识、名称、价格、库存等属性。

• 订单:拥有唯一标识、下单时间、状态等属性。订单包含多个订单项。

2. 值对象:

• 地址:拥有省、市、区、详细地址等属性。

3. 领域事件:

• 订单创建事件:当用户下单时触发该事件,包含订单信息、商品信息等数据。

• 订单支付事件:当用户完成支付时触发该事件,包含订单信息、支付金额等数据。

• 订单发货事件:当商家发货时触发该事件,包含订单信息、快递公司、快递单号等数据。

4. 聚合根:

• 商品聚合根:包含商品实体和相关的值对象,负责商品的创建、修改、查询等操作。

• 订单聚合根:包含订单实体和相关的值对象,负责订单的创建、修改、查询等操作。

5. 对外接口服务:

• 创建订单接口:用户提交购买请求后,系统创建相应的订单,并触发订单创建事件。

• 支付订单接口:用户完成支付后,系统更新订单状态,并触发订单支付事件。

• 发货接口:商家发货后,系统更新订单状态,并触发订单发货事件。

• 查询订单接口:用户可以根据订单号等条件查询自己的订单信息。

该demo中,商品和订单是两个核心领域概念,分别由对应的聚合根负责管理。同时,通过定义领域事件,实现了不同业务场景下的数据更新和通知。最后,对外提供了一组简单的接口服务,方便系统的使用和扩展。

demo的java代码实现

好了,有了以上我们对业务场景的充分剖析,确定了子域,接下来我们该写我们的代码。

  1. 商品实体类:
// 省略getter/setter方法
public class Product {
private Long id;
private String name;
private BigDecimal price;
private Integer stock;
}

2. 订单实体类

// 省略getter/setter方法
public class Order {
private Long id;
private LocalDateTime createTime;
private Integer status;
private List orderItems;
}

3. 订单项实体类

// 省略getter/setter方法
public class OrderItem {
private Long id;
private Product product;
private Integer quantity;
private BigDecimal price;
}

4. 地址值对象

// 省略getter/setter方法
public class Address {
private String province;
private String city;
private String district;
private String detail;
}

5. 领域事件类

//订单创建领域事件
public class OrderCreatedEvent {
private Order order;
private List orderItems; public OrderCreatedEvent(Order order, List orderItems) {
this.order = order;
this.orderItems = orderItems;
}
} //订单支付领域事件
public class OrderPaidEvent {
private Order order;
private BigDecimal amount; public OrderPaidEvent(Order order, BigDecimal amount) {
this.order = order;
this.amount = amount;
}
} //订单
public class OrderShippedEvent {
private Order order;
private String expressCompany;
private String expressNo; public OrderShippedEvent(Order order, String expressCompany, String expressNo) {
this.order = order;
this.expressCompany = expressCompany;
this.expressNo = expressNo;
}
}

6. 商品聚合根

public class ProductAggregate {
private ProductService productService; public void createProduct(Product product) {
productService.create(product);
} public void updateProduct(Product product) {
productService.update(product);
} public void deleteProduct(Long productId) {
productService.delete(productId);
} public Product getProductById(Long productId) {
return productService.getById(productId);
}
}

7. 订单聚合根

public class OrderAggregate {
private OrderService orderService; public void createOrder(Order order, List orderItems) {
orderService.create(order);
// 触发订单创建事件
DomainEventPublisher.publish(new OrderCreatedEvent(order, orderItems));
} public void payOrder(Long orderId, BigDecimal amount) {
orderService.pay(orderId, amount);
// 触发订单支付事件
DomainEventPublisher.publish(new OrderPaidEvent(orderService.getById(orderId), amount));
} public void shipOrder(Long orderId, String expressCompany, String expressNo) {
orderService.ship(orderId, expressCompany, expressNo);
// 触发订单发货事件
DomainEventPublisher.publish(new OrderShippedEvent(orderService.getById(orderId), expressCompany, expressNo));
} public Order getOrderById(Long orderId) {
return orderService.getById(orderId);
}
}

总结

通过以上demo,对于实体和值对象,大家会很好理解,并且很直观。但是, 我额外想重点解释一下聚合根和领域事件的概念

1. 聚合根

从上面的demo可以看出,在合根类中,我们定义了商品和订单的增、删、查等操作,并且为订单定义了创建订单、支付订单、发货等业务逻辑代码。

聚合根是一个对象,它代表一组相关联的对象的整体。在聚合根内部,可以包含多个实体对象和值对象。聚合根通常可以通过唯一标识符来进行识别和访问。它是整个聚合的管理者,负责维护聚合之内的一致性,并协调各个实体对象之间的关系。聚合根通常具有丰富的行为和操作,可以对聚合内部的对象进行复杂的操作。

所以说,真正的聚合根内的方法是基于充血模型封装的,而不是仅仅是对对象的数据封装。在聚合根中,对象不仅封装了数据,还包含了相应的行为和业务逻辑。这意味着在一个聚合根中,对象可以自己处理自己的业务逻辑,而不需要外部的控制。就如同demo中所写的那样,订单对象可能包含一些关于订单处理和交付的方法,如确认订单、取消订单、发货等。

2. 领域事件

领域事件是DDD中最重要的概念之一,他是解决子域之间耦合的重要手段,因为它们提供了一种将领域概念和业务语言转化为代码的方法。当一个领域事件发生时,它会触发一些操作,这些操作可能会更改系统的状态,也可能会导致其他领域事件的发生。通过对领域事件进行建模,我们可以更好地了解业务过程并设计出更加符合实际需求的系统。

在DDD中,领域事件通常由三个部分组成:

  1. 事件名称:这个名称应该能够简洁明了地描述事件所代表的业务意义。

  2. 相关数据:这些数据包含了事件发生时与事件相关的所有信息。例如,在一个电子商务系统中,如果订单被提交,则订单信息以及买家和卖家的信息都应该包括在该事件中。

  3. 发送者和接收者:发送者通常是触发事件的对象,接收者则是事件处理的对象。

领域事件在DDD中有很多用途。例如,它们可以用来触发其他业务流程、更新数据库或通知其他子系统。它们还可以用于解决一些复杂的业务逻辑问题,例如并发、数据同步和错误处理等等。

总之,领域事件是DDD架构中非常重要的概念,它可以帮助我们更好地理解业务过程,设计出更加符合实际需求的系统,并提高系统的可维护性和可扩展性。

关于聚合根,领域事件的那点事---深入浅出理解DDD的更多相关文章

  1. 004-spring-data-elasticsearch 3.0.0.0使用【二】-spring-data之定义方法、创建repository实例、从聚合根发布事件

    续上文 1.4.定义方法 存储库代理有两种方法可以从方法名称派生特定于存储的查询.它可以直接从方法名称派生查询,或者使用手动定义的查询.可用选项取决于实际store.但是,必须有一个策略来决定创建什么 ...

  2. DDD理论学习系列(9)-- 领域事件

    DDD理论学习系列--案例及目录 1. 引言 A domain event is a full-fledged part of the domain model, a representation o ...

  3. ASP.NET Core Web API下事件驱动型架构的实现(四):CQRS架构中聚合与聚合根的实现

    在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅.通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件总线的实现.接下来对于事件驱动型架构的讨论,就需 ...

  4. NET Core Web API下事件驱动型架构CQRS架构中聚合与聚合根的实现

    NET Core Web API下事件驱动型架构在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅.通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件 ...

  5. Abp 领域事件简单实践 <四> 聚合根的领域事件

    聚合根有个 DomainEvents 属性. 首先聚合根是一个实体.这个实体的仓储有变化(增删改)的时候,会触发这个DomainEvents 里的事件.就像EventBus.Trigger一样. pu ...

  6. 初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob存储

    Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章. 目录 前言 开始 聚合根 仓储 领域服务 BLOB存储 应用服务 单元测试 模块引用 最后 前言 在前两节中介绍了ABP模块开发的基本步 ...

  7. 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则

    目录 前言 聚合 聚合和聚合根原则 包含业务原则 单个单元原则 事务边界原则 可序列化原则 聚合和聚合根最佳实践 只通过ID引用其他聚合 用于 EF Core 和 关系型数据库 保持聚合根足够小 聚合 ...

  8. 关于ABP聚合根类AggregateRoot的思考

    AggregateRoot和Entity的区别 AggregateRoot继承于Entity,并实现了IGeneratesDomainEvents接口 public class AggregateRo ...

  9. ABP官方文档翻译 3.7 领域事件(事件总线)

    领域事件(事件总线) 事件总线 注入IEventBus 获取默认实例 定义事件 预定义事件 处理异常 实体更改 触发事件 处理事件 处理基础事件 处理者异常 处理多个事件 注册处理者 自动 手动 取消 ...

  10. 从壹开始微服务 [ DDD ] 之六 ║聚合 与 聚合根 (下)

    前言 哈喽大家周二好,上次咱们说到了实体与值对象的简单知识,相信大家也是稍微有些了解,其实实体咱们平时用的很多了,基本可以和数据库表进行联系,只不过值对象可能不是很熟悉,值对象简单来说就是在DDD领域 ...

随机推荐

  1. PASS模型需求分析和原型设计

    班级网址 https://edu.cnblogs.com/campus/zjcsxy/SE2020 作业要求 https://edu.cnblogs.com/campus/zjcsxy/SE2020/ ...

  2. 如何让charles无论怎么请求都返回一个结果

    1. map Local         将匹配的url映射到本地文件.这个需要首先将url右键,save Response,将原有报文保存到本地,然后映射到该文件,修改该文件即可,直接自己写费事2. ...

  3. 2020.11.24 typeScript命名空间

    命名空间:定义了标识符的可见范围,一个标识符可以在多个命名空间中定义,它在不同命名空间的含义是互不相干的.在一个新的命名空间可以定义任何新的标识符,它不会与已有的任何标识符发生冲突. 使用: 这个时候 ...

  4. MYSQL DUAL(伪表)

    #DUAL是一个伪表,不存在的表. SELECT 8*9 FROM DUAL #输出72

  5. vlan概述

    一.vlan是什么? vlan顾名思义就是虚拟局域网Virtual Local Area Network,为什么要划分vlan?划分vlan可以控制广播,增强网络安全性,简化网络管理. 二.vlan的 ...

  6. 看了还不懂b+tree的本质就来打我

    看了还不懂b+tree的本质就来打我 大家好,我是蓝胖子. 今天我们来看看b+tree这种数据结构,我们知道数据库的索引就是由b+tree实现,那么这种结构究竟为什么适合磁盘呢,它又有哪些缺点呢? 我 ...

  7. Goravel ORM 新增模型关联,用 Golang 写关联也可以跟 Laravel 一样简单

    关于 Goravel Goravel 是一个功能完备.具有良好扩展能力的 Web 应用程序框架.作为一个起始脚手架帮助 Golang 开发者快速构建自己的应用.框架风格与 Laravel 保持一致,让 ...

  8. Java输入与输出语句详细解析

    前言 我们在前面的文章中,学习了如何创建Java文件,也学习了Java里的标识符.运算符.进制等内容.有些同学觉得还不够过瘾,给壹哥发来了好多私信,期待我能够把进度更新的再快一点.这不,新内容这就给大 ...

  9. Salesforce CPQ之后续慢慢看系列

    salesforce核心两朵云,sales & service. 针对sales的quote / quote line item的报价功能,还是相对薄弱.针对sales,报价的准确性影响着成单 ...

  10. Java多线程(一篇从0讲透)

    多线程 思维导图看天下: 1. 概述 并行与并发 并行 :指两个或多个事件在同一时刻发生(同时发生) 并发 :指两个或多个事件在同一个时间段内发生.(交替执行) 线程与进程 进程:是指一个内存中运行的 ...