You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DDD:概念一致但验证规则不同的共享Value Object如何设计?

问题背景

我已经阅读过两个关于DDD值对象的相关讨论,当值对象(VO)可以完全共享时,这些内容都合理。

我现在的场景是多个领域实体都用到“名称”这个值对象概念,当前的代码实现如下:

public record Name
{
    public string Value { get; }

    private const uint MinLength = ValidationConstants.MinNameLength;

    private const uint MaxLength = ValidationConstants.MaxNameLength;

    public Name(string value)
    {
        value = value.Trim();

        if (!value.LengthIsBetween(MinLength, MaxLength))
        {
            throw new InvalidResourceNameException(
                $"Name must be provided and be between {MinLength} and {MaxLength} characters (inclusive)"
            );
        }

        Value = value;
    }
}

但不同实体对名称的最小/最大长度限制不一样。我可以在构造Name时传入长度参数,但觉得这样不符合VO应该维护自身不变量的原则(如果我的理解有误请指正)。

请问哪种方案更符合DDD理念:是为每个实体单独定义对应的Name VO,还是先定义一个基础Name VO,再扩展出满足各实体特定需求的VO,比如:

public record EntityName : Name
{
  // 实体特定逻辑
}

解答

首先明确核心原则:值对象的不变量必须内聚且固定——它代表领域中一个明确的概念,约束条件是这个概念本身的固有属性,而非依赖外部传入的参数。所以构造时传入长度参数的方案确实不符合DDD中VO的设计原则,这会让VO的不变量失去确定性,违背了值对象作为领域概念载体的意义。

针对两种可选方案分析如下:

1. 为每个实体单独定义专属的Name VO

这是最贴合DDD理念的方案。不同实体的“名称”在业务语境中本质是不同的领域概念:比如CustomerName和ProductName,长度限制的差异源于它们在业务中的定位——客户名称可能需要容纳更多字符,产品名称则要求简洁易识别。每个专属VO维护自身固定的不变量,清晰对应特定领域概念,避免共享带来的歧义,同时提升代码的可读性和维护性。

示例代码:

public record CustomerName
{
    public string Value { get; }
    private const uint MinLength = 2;
    private const uint MaxLength = 50;

    public CustomerName(string value)
    {
        value = value.Trim();
        if (!value.LengthIsBetween(MinLength, MaxLength))
        {
            throw new InvalidCustomerNameException(
                $"客户名称长度必须在{MinLength}到{MaxLength}字符之间(包含边界值)"
            );
        }
        Value = value;
    }
}

public record ProductName
{
    public string Value { get; }
    private const uint MinLength = 1;
    private const uint MaxLength = 20;

    public ProductName(string value)
    {
        value = value.Trim();
        if (!value.LengthIsBetween(MinLength, MaxLength))
        {
            throw new InvalidProductNameException(
                $"产品名称长度必须在{MinLength}到{MaxLength}字符之间(包含边界值)"
            );
        }
        Value = value;
    }
}

2. 继承基础Name VO扩展专属VO

这种方案需谨慎使用。DDD中值对象强调值的相等性,继承很可能破坏这一语义——比如EntityName和Name实例的相等性判断会出现不符合预期的结果。此外,基础VO若承载通用逻辑,容易演变成模糊领域边界的“万能抽象”。即便要采用这种方式,也必须确保基础VO仅提供纯通用工具方法(如字符串修剪),不变量完全由子类定义,但这种做法的收益远不如单独定义VO清晰。


总结:优先选择为每个实体定义专属的Name值对象,这最贴合DDD“值对象对应明确领域概念”的核心思想,让领域规则的表达更清晰、内聚。

内容的提问来源于Stack Exchange,提问作者Ruben Hart

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 17:15:16