如何运用领域驱动设计 - 聚合

本文深入探讨了领域驱动设计中的聚合概念,通过一个旅行记账的案例,展示了如何识别和划分聚合,以及聚合根的角色。文章指出,聚合是解决复杂关联关系的手段,封装了相关对象并以一个根实体为中心。聚合根保护内部对象,通过ID引用与其他聚合交互,确保一致性。此外,文章强调了避免过大的聚合,以保证性能,并讨论了聚合在一致性维护中的作用。
摘要由CSDN通过智能技术生成

概述

在前几篇的博文中,我们已经学习到了如何运用实体和值对象。随着我们所在领域的不断深入,领域模型变得逐渐清晰,我们已经建立了足够丰富的实体和值对象。但随着实体和值对象的数量逐渐增多,它们之间的关系也显得越来越复杂:实体A与实体B存在一对一的关系,实体B又与实体C存在一对多的关系。就这样一层套一层,本来约束已经足够好的领域对象们彷佛已经开始对我们不太友好。为了处理这一系列的问题,我们需要将一些实体和值对象划分在一个统一的边界内,原来存在多重关联关系的大模型被分解为较小的领域对象群。

而这种强有力的划分手法就是领域驱动设计战术模式中的“聚合”。可能大家已经听过它的一个重要部分“聚合根”,那么我们什么情况下考虑使用聚合根呢?聚合根又是从什么地方来?聚合与实体之间又有什么关系?如何确定和划分一个合理的聚合?本文将从不同的角度来带大家重新认识一下“聚合”这个概念,并且给出相应的代码片段(本教程的代码片段都使用的是C#,后期的实战项目也是基于 DotNet Core 平台)。

何为聚合

还是先来看看原著《领域驱动设计:软件核心复杂性应对之道》 中对聚合的有关解释:

在具有复杂关联的模型中要想保证对象更改的一致性是很困难的。不仅互不关联的对象需要遵守一些固定规则,而且紧密关联的各组对象也要遵守一些固定规则。然而,过于谨慎的锁定机制又会导致多个用户之间臺无意义地互相干扰,从而使系统不可用。
首先,我们需要用一个抽象来封装模型中的引用。AGGREGATE就是一组相关对象的集合,我们把它作为数据修改的单元。每个AGGREGATE都有一个根(root)和一个边界(boundary).边界定义了AGGREGATE的内部都有什么。根则是AGGREGATE中所包含的一个特定Entity。在AGGREGATE中,根是唯一允许外部对象保持对它的引用的元素,而边界内部的对象之间则可以互相引用。除根以外的其他Entity都有本地标识,但这些标识只有在AGGREGATE内部才需要加以区别,因为外部对象除了根Entity之外看不到其他对象。

演化案例

还记得我们在上一篇博文 如何运用领域驱动设计 - 实体 中所展开的一个关于旅行记账的案例吗? 在学习实体的时候,我们已经构建了一个叫做Itinerary的实体,并且赋予了它应用的行为操作。 到目前为止,我们那个案例好像还和主题离的稍微有点远,我们虽然实现了行程这个东西,但是怎么记账呢?

接下来,让我们完善这个案例,让它更贴近于我们真实的项目需求:

当用户创建一个行程时,则证明该旅程的账单已经被开启了。创建该行程的用户被认定为管理员,他可以添加参与该行程的小伙伴。所有参与行程的小伙伴,都可以在旅行的过程中记账(比如小伙伴C和小伙伴A吃了一顿火锅花了300块钱,小伙伴C则可以记入本笔开销,而该笔开销的参与者是小伙伴C和A),当大家旅行完成了之后就可以进行结算,将费用平摊到每个人身上,谁需要补钱,谁需要退钱等都可以被该应用计算出来。

这是简化后的版本,为的是希望大家能大致明白我们需要做一个什么样的东西,并且如何用我们所学到的领域驱动设计知识来建模和编码,为了让大家更清晰的理解需求,我粗浅的为大家绘制了一个原型图:

发现实体关系

根据需求描述,再结合我们已有的领域设计知识,我们马上就能找出另外一个重要的实体对象出来。没错,那就是账单。在这个案例中,我们暂定将账单命名为记账薄Account book)。在第二个原型图中,我们大致能够理解记账薄是一个什么东西,它记录了行程中所有的开销内容和开销金额。这一行一行的开销信息,我们将它命名为开销项Overhead item)。这里为了简化起见,我们忽略了每条开销项中的其它信息,例如参与人员,参与地点等等。

接下来,我们来分析已经发现的两个事物:记账薄开销项。先来说开销项吧,它是属于实体还是值对象呢?结合前两篇博文中我们说学到的内容,它需要一个ID来辨识它吗? 也许还是有些困惑,因为好像它不像性别、姓名这一类东西具有很明显的无ID特征。所以我们需要来识别该对象拥有的属性:开销内容、开销金额、开销时间。“在2019年10月12日,买了一个冰糕花费了3元人民币”,在我们当前的领域,我们需要使用一个ID来区分它吗?很显然我们是需要的,我们不能说只要在同一时间花了同样的钱买了同样的东西就是一样的东西了,比如用户A在行程A中和用户B在行程B中同时间同样的钱买了同样的东西,我们会认为是一样的吗?很显然,不能。所以开销项是一个实体。那么记账薄呢?很显然,它也是一个实体,我们需要通过ID来识别到底是哪个记账薄。

此时我们已经捕获出了两个实体对象:记账薄开销项。而且可以清楚的看到,它们之间是一个一对多的关系。然后来尝试将它们转换为我们熟悉的C#代码吧:


public class AccountBook
{
   
    public Guid ID {
    get; private set; }

    public List<OverheadItem> OverheadItems {
   private get;private set; }

    //ctor

    // 记账薄的行为
    // ....
}

public class OverheadItem
{
   
    public Guid ID {
    get
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值