如何在进行域驱动设计时正确使用值对象验证?

时间:2013-01-22 12:16:58

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

我在伪代码中有一个简单的实体 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??!!
    }
}

和类数量例如将是:

class Quantity {

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

}

现在问题:

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

3 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

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

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

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

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

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

答案 2 :(得分:0)

  1. 聚合根应该在其聚合中强制执行不变量,但它们不会进行所有验证。特别是在施工时没有验证,通常在施工人员或工厂处理。 事实上,尽可能多地(非特定于上下文的)不变量移动到构造函数和工厂可能是有益的。我认为拥有always valid实体更好,而不是依赖于在聚合根或实体本身上重复使用ValidateThis()ValidateThat()方法。

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

  3. 总是有效的实体的精神相同,价值对象最好是不可变的,这意味着你只需要在新的时候验证一次。然而,这是内在验证,您可以在包含实体中完美地进行外围验证(例如,您的列表中不能有超过3个这样的值对象值对象A始终与价值对象B 等相关。)

  4. 这是情境验证,而不是对ShippingDate内在不变量的验证。因此,Order应负责独立于每个值对象的有效性检查ShippingDate >= OrderDate

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