领域设计之理解聚合与聚合根!

简介: 领域设计之理解聚合与聚合根!

先从一个简单的例子出发,进行讲解!

数据驱动建模设计举例

假设有如下几张表:

  • order(订单)和order_detail(订单明细)
  • product(商品)和product_comment(商品评论)

正常来说,从表的设计看,能知道:

  • orderorder_detail之间肯定是一对多的关系。
  • productproduct_comment之间肯定是一对多的关系。

那么他们的关系实际是一样的吗?

需要深入到代码,才能够发现差异:

public class OrderService {
    @Transactional
    public void createOrder(Order order, List<OrderDetail> orderDetailList) throws Exception {
        // 保存订单
        // 保存订单详情
    }
}
public class ProductService {
    @Transactional
    public void createProduct(Product product) throws Exception {
        // 保存产品
    }
}

订单和订单明细是一起保存的,也就是说两者可以作为一个整体来看待(聚合

  • 而产品和产品评论之间并不能被看做一个整体,所以没有在一起进行操作!

这层逻辑,只看表设计是看不出来的,只有看到代码了,才能理清这一层关系。

  • 这无形中就增加了理解和使用难度。

聚合就是解决这种问题的一种方法!

不过这里也能看出架构与代码之间的差异!

在《程序员必读之软件架构》书中有这么一段话:

很多人以组件来谈论软件系统,然而代码通常并未反映出这种结构。

这就是软件架构和依据原则编码之间会脱节的原因之一。

  • 简单说就是:墙上的架构图说的是一回事,代码说的却是另一回事。

这也是架构与代码差异的一个原因。

还有一个原因就是某些约束没有在设计中体现出来,而这些约束需要阅读代码才能够知道。

  • 这就增加了理解和使用这个组件的难度。

这个问题在基于数据建模的设计方法上比较明显(以上例子也能说明问题)。

使用聚合前:

可以看出依赖关系十分混乱!

使用聚合后:

什么是聚合和聚合根

一段简单的代码:

public class Test {
    public void test() {
        System.out.println("test1");
        System.out.println("test2");
    }
}

上面的代码,我们如何保障在多线程情况下1和2能按顺序打印出来?

最简单的方法就是使用synchronized关键字进行加锁操作。

public class Test {
    public synchronized void test() {
        System.out.println("test1");
        System.out.println("test2");
    }
}

synchronized 保证了代码的原子性执行。

如果说,synchronized是多线程层面的锁。

  • 事务是数据库层面的锁,那么 聚合 可以理解为业务层面的锁。

在业务逻辑上,有些对象需要保持操作上的原子性,否则就没有任何意义。

  • 这些对象就组成了聚合

比如上面的订单与订单详情,从业务上来看,订单与订单明细需要保持业务上的原子性操作:

  • 订单必须要包含订单明细。
  • 订单明细必须要属于某个订单。
  • 订单和订单明细被视为一个整体,少了任何一个都没有意义。

所以:

订单和订单明细组成一个 聚合

订单是操作的主体,所以订单是这个 聚合 的 聚合根

所有对这个 聚合 的操作,只能通过 聚合根 进行

而产品和产品评价就不构成聚合

  • 虽然在表设计时,订单和订单明细的结构关系与产品与产品评价的结构关系是一样的。

因为:

  • 虽然产品评价需要属于某个产品。
  • 但是产品不一定就有产品评价。
  • 产品评价可以独立操作。

产品和产品评论是两个聚合

  • 产品评论通过productId产品聚合进行关联

如何确定聚合和聚合根

对象在业务逻辑上是否需要保证原子性操作是确定聚合和聚合根的其中一个约束。

还有一个约束就是 边界,即聚合多大才合适?

  • 过大的 聚合 会带来各种问题。

比如下面的代码:

public class Test {
    public synchronized void test() {
        System.out.println("test1");
        System.out.println("test2");
        System.out.println("test3");
        System.out.println("test4");
    }
}

如果只希望1,2能按顺序打印出来,而3和4没有这个要求。

上面的代码能满足要求,但是影响了性能。

  • 优化方式是使用同步块,缩小同步范围。
public class Test {
    public void test() {
        synchronized(Test.class) {
            System.out.println("test1");
            System.out.println("test2");  
        }
        System.out.println("test3");
        System.out.println("test4");
    }
}

边界就像上面的同步块一样,只将需要的对象组合成聚合

假设上面的产品和产品评论构成了一个聚合。

那会发生什么事情呢?

当A,B两个用户同时对这个商品进行评论,A先开始评论,此时就会锁定该产品对象以及下面的所有评论。

  • 在A提交评论之前,B是无法操作这个产品对象的,显然这是不合理的。

如何设计聚合

假设是一个订单管理系统。

  • 订单(Order)聚合是一个典型的聚合根。

步骤如下:

  • 确定聚合的边界:
  • 订单聚合包含订单、订单明细(Order Detail)和订单支付(Order Payment)等实体和值对象。
  • 确定聚合根:
  • 在订单聚合中,订单(Order)被选为聚合根。
  • 定义聚合的属性和行为:
  • 订单聚合可以有属性如订单号、订单日期、订单状态等,行为如修改订单状态、添加订单明细等。

聚合设计的原则

聚合是用来封装真正的不变性,而不是简单的将对象组合在一起。

聚合应尽量设计的小。

聚合之间的关联通过聚合根ID,而不是对象引用。

聚合内强一致性,聚合之间最终一致性。

最后

觉得有收获,希望帮忙点赞,转发下哈,谢谢,谢谢

微信搜索:月伴飞鱼,交个朋友,进面试交流群

公众号后台回复666,可以获得免费电子书籍

参考资料

  • 《领域驱动设计:软件核心复杂性应对之道》
  • 《实现领域驱动设计》
相关文章
|
2天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1517 4
|
29天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
5天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
492 19
|
2天前
|
存储 SQL 关系型数据库
彻底搞懂InnoDB的MVCC多版本并发控制
本文详细介绍了InnoDB存储引擎中的两种并发控制方法:MVCC(多版本并发控制)和LBCC(基于锁的并发控制)。MVCC通过记录版本信息和使用快照读取机制,实现了高并发下的读写操作,而LBCC则通过加锁机制控制并发访问。文章深入探讨了MVCC的工作原理,包括插入、删除、修改流程及查询过程中的快照读取机制。通过多个案例演示了不同隔离级别下MVCC的具体表现,并解释了事务ID的分配和管理方式。最后,对比了四种隔离级别的性能特点,帮助读者理解如何根据具体需求选择合适的隔离级别以优化数据库性能。
179 1
|
8天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
21天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
9天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
448 5
|
7天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
313 2
|
23天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
25天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2608 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析