【问题标题】:How to utilize Value Objects validation properly when doing Domain Driven Design?在进行领域驱动设计时如何正确利用值对象验证?
【发布时间】:2013-01-22 12:16:58
【问题描述】:

我有一个简单的 实体 Order 伪代码:

class Order{    
    private int quantity;
    private Date orderDate;
    private Date shippingDate;

    public Order(int quantity, Date orderDate, Date shippingDate){
        if(quantity <= 0){ throw new Exception("Invalid quantity")}
        if(shippingDate < orderDate){ throw new Exception("Invalid shippingDate")}
        if(...more validation...){....throw Exceptions...}

       //assign values if everything is OK
    }
}

description、quantity、orderDate 和 shippingDate 都是从 Web 表单中读取的,每个表单都是由多个验证器配置的文本字段:

quantityField= new TextField('txt_quantity');
quantityFiled.addNotNullValidator().addNumaricValidator().addPositiveIntegerValidator()

如您所见,验证逻辑在 TextField 验证和实体验证之间重复。
我试图通过创建Quantity 类、OrderDate 类和ShippingDate 类来将值对象的概念引入我的实体。所以我的Order 实体变成了这样:

class Order{    
    private Quantity quantity;
    private OrderDate orderDate;
    private ShippingDate shippingDate;

    public Order(Quantity quantity, OrderDate orderDate, ShippingDate shippingDate){
        //assign values without validation I think??!!
    }
}

例如 Quantity 类将是:

类数量{

private int quantity;
public Quantity(int quantity){
        if(quantity <= 0){ throw new Exception("Invalid quantity")}
        this.quantity=quantity;
}

}

现在问题:

  1. Aggregate Roots 不应该负责验证整个聚合吗?我的Quantity 班级不是违反了吗?
  2. 如何在web表单验证中重用Quantity的构造函数中的验证?我认为验证代码是重复的,所以我怎样才能验证一次或至少重用验证逻辑。
  3. 由于所有值对象都将验证自己,这是否意味着我不应该验证实体中的任何内容?
  4. 由于ShippingDate 依赖OrderDate 进行验证,我应该如何验证发货日期?
  5. DDD 工厂适合所有这些吗?

【问题讨论】:

  • 你说你有一个实体Customer,但你显示的是Order的代码。
  • @DanielHilgarth 对不起我的错误。我现在修好了。
  • 在值对象之外重用验证很简单。将验证逻辑提取到返回 bool 的静态方法。如果返回 false,构造函数将调用该方法并抛出。 web层可以问Quantity.IsValid(value)
  • 如前所述,您的问题不在于拥有或不拥有对象值。不必要地添加它们不会使事情变得更容易。您需要区分两种类型的检查:1)检查是否应用了域级约束。 2) 检查用户输入,网络级验证。

标签: oop language-agnostic domain-driven-design


【解决方案1】:
  1. 如果您的域中的任何 Quantity 不能为负数,无论上下文如何,这都非常有意义
  2. 恕我直言,Quantity 构造函数中的验证用于确保应用程序正确使用类。它会引发异常,这些异常用于异常状态,而不是预期的工作流。因此,它的目的与 Web 表单验证完全不同,后者可确保用户正确使用您的应用程序。它预计无效输入并处理它。我在这里看不到真正的重复,至少不会在不违反单一职责原则的情况下消除这种重复。
  3. 我认为情况并非如此。您有 if(shippingDate &lt; orderDate) - 您打算如何在值对象中验证这一点?
  4. 啊,你看到了问题所在。相同的答案:此验证属于 Order 实体。此外,您不必为所有内容使用值对象。如果订单日期或发货日期本身没有固有限制,请继续使用Date
  5. 这似乎是一个单独的问题,我看不出与值对象有任何相关性。

【讨论】:

  • 好答案。对于第 2 点,我要补充一点,总的来说,这是一个非常有趣的问题,没有任何好的解决方案。合并验证规则会很好,但是由于语言和框架的限制,这可能很困难。示例尝试类似于 ASP.NET MVC 中的验证,它将验证属性转换为客户端 JavaScript。这种方法和相关方法的问题在于,您必须使用仅在实体被调用时才保护实体的属性。此外,验证上下文可能会影响规则本身,这是划分职责的另一个原因。
【解决方案2】:
  1. 聚合根应该在它们的聚合中强制执行不变量,但它们不会进行所有验证。尤其是在构造时不进行验证,这通常在构造函数或工厂中处理。 事实上,将尽可能多的(非特定于上下文的)不变量移动到构造函数和工厂可能是有益的。我认为拥有always valid 实体比依赖于在聚合根或实体本身上重复使用ValidateThis()ValidateThat() 方法要好得多。

  2. 基本上有 3 种验证:客户端验证、应用程序验证(在控制器或应用程序层服务中)和域验证(域层)。需要客户端验证,不能重复使用。应用程序验证可以依赖于域验证,在您的示例中,这意味着只需调用 Quantity 构造函数并处理它引发的异常。但它也可以有自己的一组特定于应用程序的非域规则 - 例如,根据其 password_confirm 验证 password 字段。

  3. 始终有效的实体非常相似,值对象最好是不可变的,这意味着您只需在新建它们时验证一次。然而,这是内在验证,您可以在包含实体中完美地进行外围验证(例如,您的列表中不能有超过 3 个此类值对象值对象 A 总是与值对象 B 等一起使用。)

  4. 这是情景验证,而不是 ShippingDate 固有不变量的验证。因此,Order 应该负责独立于每个值对象的有效性检查 ShippingDate &gt;= OrderDate

  5. 当对象的构造逻辑足够复杂以至于它本身就是一种责任,并且由于 SRP 不适合对象的构造函数或使用者时,应该使用工厂。工厂确实包含构造时验证逻辑,就像构造函数一样,这使得它们也成为各种不变的强制执行者。

【讨论】:

    【解决方案3】:

    这些问题很多,您可能希望将它们分解为单个问题。

    问题 2 到 5 在很大程度上取决于各种因素,并受制于意见。

    但这是我对问题 1 的回答(以及对问题 3 和 4 的回答):

    聚合对其完整性负责。不是聚合根。只要 Aggregate 作为一个整体保持有效,Aggregate 中的每个项目都可以进行自己的验证。

    状态验证(作为正确的数量或不是负数的数量)可以在相应的类内完成。像ShippingDate &gt;= OrderDate 这样的相互依赖的状态验证可以在更高的级别上完成,例如在聚合根中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-05
      • 1970-01-01
      • 1970-01-01
      • 2015-02-27
      • 2013-12-07
      • 1970-01-01
      相关资源
      最近更新 更多