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

关于,聚合,领域,事件,深入浅出,理解,ddd · 浏览次数 : 329

小编点评

**作者:京东物流 赵勇萍** **前言** 本文通过小demo方式介绍了DDD中战术层级的理解,即实体、值对象、聚合根和领域事件。 **实体** 实体是包含业务属性的类,如商品、订单、地址等。 **值对象** 值对象是代表特定属性的类,如商品的名称、价格、地址等。 **聚合根** 聚合根是包含多个实体和值对象的对象,负责管理和操作这些对象的行为。 **领域事件** 领域事件是触发子域之间业务逻辑的操作,并通过事件通知相关对象进行处理。 **示例** 以下是一个简单的电商订单下单的demo: ```java // 商品实体类 public class Product { private Long id; private String name; private BigDecimal price; private Integer stock; } // 订单实体类 public class Order { private Long id; private LocalDateTime createTime; private Integer status; private List orderItems; } // 订单项实体类 public class OrderItem { private Long id; private Product product; private Integer quantity; private BigDecimal price; } // 聚合根 public class OrderManager { private Order order; private List orderItems; // 创建订单 public void createOrder(Order order, List orderItems) { this.order = order; this.orderItems = orderItems; } // 触发订单创建事件 public void DomainEventPublisher.publish(OrderCreatedEvent event) { // 发送订单创建事件 } } ``` **结论** 通过这篇文章的阅读,我们可以理解实体、值对象、聚合根和领域事件等概念,并了解如何将其应用于DDD架构中。

正文

作者:京东物流 赵勇萍

前言

最近有空会跟同事讨论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相似的内容:

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

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

【numpy基础】--聚合计算

上一篇介绍的**通用计算**是关于多个`numpy`数组的计算, 本篇介绍的**聚合计算**一般是针对单个数据集的各种统计结果,同样,使用**聚合函数**,也可以避免繁琐的循环语句的编写。 # 元素的和 数组中的元素求和也就是合计值。 ## 调用方式 **聚合计算**有两种调用方式,一种是面向对象的

UML 类图几种关系(依赖、关联、泛化、实现、聚合、组合)及其对应代码

[TOC] ![image](https://img2023.cnblogs.com/blog/80824/202302/80824-20230201133344225-1260068838.png) ## 类的表示(Class) 第一层:显示类的名称,如果是抽象类,则就用斜体显示。 第二层:是类的

PromQL全方位解读:监控与性能分析的关键技术

本文全面探索PromQL,从基础语法到高级操作,详细介绍了数据聚合、时间序列分析及内置函数应用,旨在提升用户构建复杂监控策略和性能分析的能力。 关注【TechLeadCloud】,分享互联网架构、云服务技术的全维度知识。作者拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复

[转帖]Jmeter插件之ServerAgent服务器性能监控工具的安装和使用

https://www.cnblogs.com/pachongshangdexuebi/p/13354201.html 一、前言 性能测试时我们关注的重要指标是:并发用户数,TPS,请求成功率,响应时间,服务器的CPU,memory, I/O disk等。Jmeter的聚合报告可以查看并发数、吞吐量

【pandas基础】--索引和轴

在`pandas`中,索引(`index`)是用于访问数据的关键。 它为数据提供了基于标签的访问能力,类似于字典,可以根据标签查找和访问数据。 而`pandas`的轴(`axis`)是指数据表中的一个维度,可以理解为表格中的行和列。 通过指定轴,我们可以对数据进行切片、筛选、聚合等操作。 下面简要介

SQL窗口分析函数使用详解系列三之偏移量类窗口函数

1.综述 本文以HiveSQL语法进行代码演示。 对于其他数据库来说同样也适用,比如SparkSQL,FlinkSQL以及Mysql8,Oracle,SqlServer等传统的关系型数据库。 已更新第一类聚合函数类,点击这里阅读 ①SQL窗口函数系列一之聚合函数类 ②SQL窗口函数系列二之分组排序窗

基于.NetCore开发博客项目 StarBlog - (29) 开发RSS订阅功能

## 前言 最近忙中偷闲把博客的评论功能给做完了,我可以说这个评论功能已经达到「精致」的程度了😃 但在正式发布之前,先卖个关子,来介绍一下另一个新功能——RSS订阅🔊 ## RSS是啥 来自hk gov新闻网的介绍~ > RSS 是簡易資訊聚合(Really Simple Syndication

[转帖]InnoDB表聚集索引层高什么时候发生变化

导读 本文略长,主要解决以下几个疑问 1、聚集索引里都存储了什么宝贝 2、什么时候索引层高会发生变化 3、预留的1/16空闲空间做什么用的 4、记录被删除后的空间能回收重复利用吗 1、背景信息 1.1 关于innodb_fill_factor 有个选项 innodb_fill_factor 用于定义

Health Kit文档大变样,一起尝鲜!

Health Kit文档全新升级,开发场景更清晰,聚焦你关心的问题,快来一起尝鲜! 文档入口请戳:[文档入口~](https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/description-000000155