【问题标题】:Should Value Objects Contain Technical Validation For Input Parameters?值对象是否应该包含输入参数的技术验证?
【发布时间】:2017-01-06 13:23:21
【问题描述】:

正如 DDD 从业者所建议的那样,业务规则的验证必须在域对象(实体、值对象和域服务)内实现,并遵循它们自己的上下文,我读过我们应该在某个地方进行技术验证(例如检查长度、正确输入格式,正确的数据类型,...)在域模型之外和应用层之类的地方,以保持域对象清晰。

现在 我的问题是:

如果我们有信用卡号的值对象,我们是否仍应将技术验证排除在值对象之外? 换句话说,当我们处理值对象时,“自我验证”一词不涉及技术验证?

如果不正确的借记卡号码甚至电子邮件地址有可能破坏业务规则,那该怎么办?

为了更清楚,请注意这个代表借记卡号码的值对象:

    public class DebitCardNumber : ValueObject
{
    public string Number { get;private set; }

    public CreditCardNumber(string number)
    {
        Validation(number);

        this.Number = number;
    }

    private void Validation(string number)
    {
        if (String.IsNullOrWhiteSpace(number))
        {
            throw new CardNumberCanNotBeEmptyException();
        }

        if (number.Length != 16)
        {
            throw new CardNumberLengthMustBeSixteenDigitException();
        }

        int sum = 0;
        for (int i = 1; i <= 16; i++)
        {
            if (!char.IsDigit(number[i - 1]))
            {
                throw new ValueContainsSomeNonDigitCharacterException();
            }

            int m = (i % 2 == 0 ? 1 : 2);
            int a = (int.Parse(number[i - 1].ToString()) * m);

            while (a > 9)
            {
                a -= 9;
            }

            sum += a;
        }

        if ((sum % 10) > 0)
        {
            throw new ValueIsNotCorrectAsACardNumberException() 
                { Message = "Perhaps some digits has been entered in wrong order or they are incorrect." };

        }
    }
}

根据此代码,有一个验证方法可以执行算法来确定卡号的格式是否正确? 你认为它适合这种类型的验证吗?

【问题讨论】:

  • 如果值对象无效,应该无法构造。您应该将所有这些验证规则放在构造函数中,以防止创建无效对象。

标签: domain-driven-design dddd


【解决方案1】:

在我看来,您的方法在大多数情况下都是正确的。

如果号码无效,则不是真正的借记卡号码。

假设您有一个未验证的借记卡号。要么您将布尔值 valid 设置为 false,要么您的代码在欺骗您:它不是借记卡号码

如果您仍想存储数字,可能出于安全或用户体验目的,您应该在不同的对象中执行此操作,可能在 Entered Form 值对象中。

【讨论】:

  • 所以如果我采用这种方法,那么我应该在我的域对象中进行许多这样的验证。例如:不正确的电子邮件地址、过多的长姓名和家庭、空输入值等等.这里的主要问题是我如何区分域对象验证和技术验证。如果我不通过它,我提出的每个验证都可能会破坏逻辑,因此我将该验证保留在域对象中,但我想它并不完全正确。域中不能涉及哪种类型的验证?
  • 对象之间或对象外部的接缝将在较低或较高级别进行验证。例如,您不会根据数据库验证卡号,更高级别应该这样做。同样,字符串编码应该在其他地方进行验证。 Luhn 算法,肯定会检查 inside。在您的情况下,您可以在应用程序级别验证字符串是否为数字。
  • 是的,您应该将此验证放在您的域中。您提到的所有事情都是域问题。例如,某物为“空” - 如果您的域要求某物存在,则它是域规则,因此不允许为空
  • gurghet 和 tomliversidge 感谢您提供帮助的 cmets。能否请您说一些“不应该”放在域内的验证?如果你能用一些例子解释一下,那就太好了
【解决方案2】:

我不认为您的示例是技术验证规则-我认为这是借记卡的域规则-如果在银行领域中,借记卡号必须遵循某种模式,那么这是需要强制执行的域规则。

所以我认为您的解决方案是正确的。

【讨论】:

    【解决方案3】:

    正如 DDD 从业者建议的那样,业务规则的验证必须在域对象(实体、值对象和域服务)内实现

    是的。

    我还在某处读到,我们应该将技术验证(例如检查长度、正确的输入格式、正确的数据类型……)置于域模型之外,并在应用层之类的地方,以保持域对象清晰。

    这里有点困惑;重点是实体不需要担心一堆输入验证,这不是他们的工作(职责分离)。因此,我们不是将原始数据(字符串、原语)传递给模型中的实体,而是首先使用原语构造实体将识别的值类型,然后将这些实体传入。

    可以使用原语创建格式良好的值类型的规则在值类型本身(构造函数)或为此目的提供的专用工厂中正确实现)。应用程序组件有责任在将值传递给模型之前,根据它收到的消息/DTO 创建值类型。

    因此,在您的示例中,DebitCard 验证逻辑看起来是在正确的位置。

    注意 - 您的模型会随着时间的推移而演变;当模型发生变化时,您仍然需要能够读取由模型的早期版本写入的数据。添加将当前数据视为无效的验证规则可能会变得混乱 - 因此您要确保验证规则具有业务动机。您是否通过确保借记卡号具有有效的校验和来节省资金/削减成本?

    (例如:假设客户提交了带有无效卡号的采购订单。企业是要拒绝该订单,还是接受该订单但推迟到有效的付款方式执行该订单?提供?如果是后一种选择,您要确保您的验证逻辑不会妨碍接受订单)。

    【讨论】:

    • 我也很困惑。因为我读过一本官方书籍,它建议技术验证(数据类型、长度、缺少输入、格式)应该在实体之外的地方进行,它有为此目的引入了应用服务。我认为这对事务脚本有好处。关于您的注意事项:是的,当借记卡号无效时,某些业务规则将被打破。例如,当后台用户想要从借记卡中扣款时,如果借记卡号显然无效,则无法满足此请求。所以我认为最好在一开始就避免这个问题。
    • 我想您可以通过保留旧构造函数来对值对象进行版本控制,但将其标记为已弃用,甚至可能将其设为私有(如果您通过反射重构值对象)。
    【解决方案4】:

    如果我们有信用卡号等的值对象,我们仍然 是否应该将技术验证排除在我们的价值对象之外?其他 “自我验证”一词不涉及技术 处理值对象时的验证?

    正确。应该对域(实体、域服务、vos 等)进行建模,以便它们执行业务规则,而不是技术问题。您的域可能需要区分借记卡和信用卡,但我怀疑企业是否关心卡号本身的格式。卡号的格式和正确性对于基础设施目的很重要,因此可以在该层强制执行格式规则。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-04
      • 2011-07-11
      • 2011-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-13
      相关资源
      最近更新 更多