【DDD】值对象


值对象

值对象没有标识(ID)

既然值对象是没有ID的一个事物(东西),那么我们来考虑一下什么情况下我们不需要通过ID来辨识一个东西:

“在超市购物的时候:我有五块钱,你也有五块钱” 这里会关心我的钱和你的钱是同一张,同一个编码,同一个组合方式(一张五块,五张一块)吗? 显然不会。因为它们的价值是一样的,就购买东西来说,所以它是不需要ID的。

值对象是基于上下文

请注意,这是一个非常重要的前提。你会发现在上面的案例中,都有一个同样的前缀:“???的时候”。也就是说,我们考虑值对象的时候,是基于实际环境因素和语境条件(上下文)的。
实体是战术模式中同样重要的一个概念,但是现在我们先不做讨论,我们只需要明白实体是一个具有ID的事物就行了。也就是说一个同样的东西在当前环境下可能没有一个独有的标识,但可能在另一个环境下它就需要一个特殊的ID来识别它了。考虑上面的例子:

同样的五块钱,此时在一个货币生产的环境下。它会考虑这同样的一张五块钱是否重号,显然重号的货币是不允许发行的。所以每一张货币必须有一个唯一的标识作为判断。

显然,同样的东西,在不同的语境中居然有着不同的意义。当前上下文的值对象可能是另一个上下文的实体,我们只需要明白实体是一个具有ID的事物就行了。

怎么运用值对象

此时,你应该可以根据你自己的所在环境和语境(上下文)捕获出属于你自己的值对象了,比如货币呀,姓名呀,颜色呀等等。下面我们来考虑如何将它放在实际代码中。
值对象属于实体,不能独立存在。

public class MySupmarketShopping
{
    public Money Amountofmoney { get; set; } 
}
class  Money
{
    public int Amount { get; set; }
    public Currency Currency { get; set; }

    public Money(int amount,Currency currency)
    {
        this.Amount = amount;
        this.Currency = currency;
    }

    public Money ConvertToRmb(){
        int covertAmount = Amount / 6.18;
        return new Money(covertAmount,rmbCurrency);
    }
}

Money包含金额和币种,它就是一个值对象

请注意:在这个行为完成后,我们是返回了一个新的Money对象,而不是在当前对象上进行修改。这是因为我们的值对象拥有一个很重要的特性,不可变性。
值对象是不可变的:一旦创建好之后,值对象就永远不能变更了。相反,任何变更其值的尝试,其结果都应该是创建带有期望值的整个新实例。

值对象持久化

有关值对象持久化的问题一直是一个非常棘手的问题。这里我们提供了目前最为常见的两种实现思路和方法供参考。而该方法都是针对传统的关系型数据库的。(因为Nosql的特性,所以无需考虑这些问题)

  • 将值对象映射在表的字段中
    我们最后持久化出来的结果比较类似于这样:
  • 将值对象单独用作表来存储
    方式在持久化时将值对象单独存为一张表,并且以依赖对象的ID主为自己的主键。在获取时用Join的方式来与依赖的对象形成关联。

如何实现值对象(EF Core)

使用Fluent API中的OwnsOne()等方法来配置。

//聚合根
public record User : IAggregateRoot
{
    public Guid Id { get; init; }
    public PhoneNumber PhoneNumber { get; private set; }
}
//值对象
public record PhoneNumber(int RegionCode,string Number);
class UserConfig : IEntityTypeConfiguration
{
    public void Configure(EntityTypeBuilder builder)
    {
        builder.OwnsOne(x => x.PhoneNumber, nb => {
            nb.Property(x => x.RegionCode).HasMaxLength(5).IsUnicode(false);
            nb.Property(x => x.Number).HasMaxLength(20).IsUnicode(false);     
        });
    }
}

在EF Core中,实体的属性可以定义为枚举类型,枚举类型的属性在数据库中默认是以整数类型来保存的。EF Core中可以在Fluent API中用HasConversion()把枚举类型的值配置成保存为字符串。

参考:

相关