驱动领域设计(DDD)中聚合根、实体、值对象

聚合根、实体、值对象

前言

在领域驱动设计(Domain-Driven Design,简称DDD)中,实体(Entity)、值对象(Value Object)和聚合根(Aggregate Root)是构成业务模型的核心概念,它们帮助开发者构建出与业务领域紧密相关的模型。下面将分别解释这些概念,并给出定义和示例。


实体(Entity)
定义

实体是拥有唯一标识和连续存在的对象。

实体是具有唯一标识符(ID)的对象。即使两个实体的属性完全相同(无法通过几个属性的组合来确定唯一对象),只要它们的ID不同,它们也被视为不同的实体。实体的生命周期和行为是领域设计中的重要方面。

举例

在这个例子中,User 是一个实体,每个 User 都有一个唯一的 UserId。即不管姓名、邮箱以及电话等信息如何变,只要通过 Id (比如身份证号)就可以确定一个人(数据库表中的一条记录)。

public class User {
    private Long id;
    private String name;
    private String email;
    private String phone;
    private String idCard;
    
    // Constructor、Getters、setters
}

值对象(Value Object)
定义

值对象是没有唯一标识,只通过它的属性值来定义的对象。

值对象是描述事物的属性,但没有唯一标识符的对象。值对象应该是不可变的,这意味着一旦创建,其属性就不应该改变。

举例

在这个例子中,Money 是一个值对象,它通过货币类型和金额来定义。即无法通过一个字段就确定是什么币种以及金额,必须通过币种和金额在一起才能判断到底有多少钱。

常见的还有地址,通过国家、省份、城市等来确定某一个具体地区,无法通过一个唯一标识就确定是哪个地方。

public class Money {
    private Currency currency;
    private BigDecimal amount;

    // Constructor、Getters、setters
}
public class Address {
    private final String street;
    private final String city;
    private final String state;
    private final String zipCode;

		// Constructor、Getters、setters
}

聚合根(Aggregate Root)
定义

聚合根是一组相关对象的集合,作为数据修改和事务性操作的边界。

聚合根是一组实体和值对象的集合,这些对象一起作为一致性和交易性的边界。聚合根负责维护聚合的一致性,并且是外部对象与聚合内部对象交互的唯一入口(Domain层)

举例

在这个例子中,Order 是一个聚合根,它包含了用户(User)、订单项(OrderItem)和收货地址(Address),并且维护了订单的总金额(total)。其中,User 和 OrderItem 是实体,Money 和 Address 是值对象。

public class Order {
    private Long id;
 	  private User user;
    private List<OrderItem> items;
    private Address shippingAddress;
    private Money total;

    // 聚合根中是可以定义相关行为的,即充血模型
    public void addItem(OrderItem item) {
        items.add(item);
        recalculateTotal();
    }

    private void recalculateTotal() {
        total = items.stream()
                     .map(OrderItem::getTotalPrice)
                     .reduce(Money.ZERO, Money::add);
    }

    // Constructor、Getters、setters
}

延伸
实体和值对象的区别

上面有提到,实体能够通过唯一标识来确定一个对象(一条数据库记录),不管是用户还是订单,都能通过一个字段来确定是哪个人、哪笔订单;而值对象没有唯一标识,是通过全部字段来确定一个对象(一条记录)的,像地址、金额,必须同时指定币种和数值才能确定有多少钱。

关于数据库表中的唯一键的理解:

唯一键(几个字段)可以确定唯一一条记录,此时可以考虑其是否可以作为一个值对象;而不能说通过唯一键能确定一个实体对象,因为唯一键相同,但是主键不同,所以就不是同一个对象。

为什么在聚合根中为什么是引用实体和值对象

在领域驱动设计(DDD)中,聚合根(Aggregate Root)引用实体(Entity)和值对象(Value Object)而不是重新定义它们的属性,原因包括但不限于以下几点:

  1. 封装性:每个实体和值对象都应该对其内部状态负责。通过引用而不是复制属性,聚合根尊重了对象的封装性,避免在多个地方重复相同的数据和逻辑。

  2. 单一职责原则:聚合根的职责是维护事务的一致性和完整性,而不是管理实体或值对象的内部实现细节。每个实体和值对象应该负责管理自己的状态和行为。

  3. 减少重复:避免在聚合根中重新定义实体和值对象的属性可以减少代码重复,使得模型更加清晰和易于维护。

  4. 灵活性:实体和值对象可以在多个聚合中使用,而不必依赖于聚合根的具体实现。这样可以提高模型的灵活性和可重用性。

  5. 引用透明性:通过引用实体和值对象,聚合根可以利用它们的内在行为和规则,例如值对象的相等性比较或实体的唯一标识。

  6. 维护一致性:如果聚合根中复制了实体和值对象的属性,任何对实体或值对象的更改都需要在聚合根中同步更新,这增加了维护的复杂性。通过引用,聚合根可以确保总是使用最新的实体和值对象状态。

  7. 领域模型的表达力:DDD旨在创建一个丰富的领域模型,通过引用实体和值对象,可以更准确地表达领域概念和它们之间的关系。

  8. 避免副作用:直接在聚合根中修改实体或值对象的属性可能会导致不可预见的副作用,尤其是在并发环境中。通过引用,可以更好地控制对象间的影响。

  9. 优化性能:在某些情况下,复制大量数据可能会导致性能问题。通过引用,可以减少内存的使用和提高性能。

为什么在聚合根中不是将实体和值对象的属性在聚合根中再定义一遍

在聚合根中引用实体和值对象而不是重新定义它们的属性,是遵循领域驱动设计(DDD)原则的一种做法。这种做法的优点已经在之前的回答中提及。下面我将详细说明如果不这么做,即在聚合根中重新定义实体和值对象的属性,可能会带来的一些缺点:

  1. 数据不一致性:如果在聚合根中复制了实体和值对象的属性,任何对实体或值对象的内部状态的更改都需要在聚合根中同步更新,否则可能导致数据不一致。

  2. 代码重复:重新定义属性会使得相同的数据在系统中多处出现,增加了代码的重复性,这违背了DRY(Don’t Repeat Yourself)原则。

  3. 维护难度增加:随着系统的发展,维护多个相同数据的地方将变得非常困难,任何对数据结构的更改都需要在多个地方同步,增加了出错的风险。

  4. 违反封装性:每个类应该对其内部数据负责,聚合根重新定义实体和值对象的属性,破坏了封装性,使得聚合根与实体和值对象的内部实现耦合。

  5. 降低灵活性和可重用性:实体和值对象可能在多个聚合根中使用,如果在聚合根中重新定义了它们的属性,那么这些实体和值对象的可重用性将大大降低。

  6. 增加存储和传输成本:复制属性意味着在存储和传输数据时,相同的数据可能需要多次存储和传输,这增加了存储成本和网络传输成本。

  7. 违反单一职责原则:聚合根承担了过多的职责,不仅要管理自己的逻辑,还要管理实体和值对象的状态,这违背了单一职责原则。

  8. 并发问题:在多线程环境中,如果聚合根直接操作实体和值对象的属性,可能会导致并发访问的问题。

  9. 难以扩展:随着业务逻辑的发展,聚合根内部复制的实体和值对象的属性可能需要扩展,这将导致在聚合根中进行大量的修改,使得系统难以扩展。

  10. 测试复杂性:当聚合根直接包含实体和值对象的属性时,对聚合根的单元测试可能需要构建完整的实体和值对象实例,增加了测试的复杂性。

  11. 违反业务规则:实体和值对象可能包含特定的业务规则,如值对象的不可变性和实体的生命周期管理,聚合根重新定义这些属性可能会使得这些规则难以实施。

  12. 性能问题:对于大型对象,复制属性可能会导致内存使用增加,影响系统性能。

通过引用实体和值对象,而不是复制它们的属性,可以避免上述缺点,同时也能够更好地体现DDD的原则,如模型的丰富性、封装性、单一职责原则等,从而创建出更加清晰、灵活和可维护的系统。


  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DDD领域驱动设计)是一种软件设计思想,其包含了四个重要的概念:实体对象聚合领域服务。 1. 实体(Entity) 实体是指具有唯一标识的对象,它具有生命周期,并且在系统需要被跟踪和修改。实体通常是通过唯一标识来进行识别和区分的。例如,订单、用户、商品等都可以是实体。 例如,订单实体包含了订单号、下单时间、订单状态等属性,同时具有一些操作,比如修改订单状态、添加订单项等。 2. 对象(Value Object) 对象是指没有唯一标识的对象,它的属性可以改变,但是不会改变对象的身份。对象通常作为实体的属性存在,比如订单的收货地址、商品的价格等。对象通常是不可变的,即创建后不可修改。 例如,收货地址对象包含了姓名、电话、地址等属性,它们的可以改变,但是这个地址本身并没有唯一标识。 3. 聚合(Aggregate Root) 聚合是指一组具有关联关系的实体对象的集合,其一个实体作为聚合,负责管理整个聚合聚合可以保证整个聚合的完整性和一致性。在聚合内,实体只能通过聚合来进行访问和修改。 例如,订单聚合包含了订单实体、订单项实体以及收货地址对象,订单实体聚合,通过订单实体管理整个聚合。 4. 领域服务(Domain Service) 领域服务是指在领域模型,不属于任何实体对象的操作,它们通常是跨实体的业务操作,或者是需要进行复杂计算的操作。 例如,计算订单的总金额就是一个领域服务,它需要查询订单的所有订单项,并计算每个订单项的金额,最后求和计算出订单的总金额。 总之,DDD实体对象聚合领域服务是设计领域模型的重要概念,它们能够帮助我们构建具有高内聚、低耦合、易扩展的领域模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值