What is the correct way to use validation of Value objects when running a domain-managed project?

I have a simple Entity Order

in pseudocode:

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
    }
}

      

the description, quantity, orderDate and shippingDate are all read from a web form, where each is a text field that is configured by multiple validators:

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

      

As you can see, validation logic is duplicated between TextField

Entity validation and validation.
I tried to represent the concept of a value object for my object by creating a Quantity

class, OrderDate

class and ShippingDate

. So my object Order

looks like this:

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??!!
    }
}

      

and class Number, for example, would be:

Class Quantity {

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

      

}

Now the questions:

  • Aren't the aggregate roots responsible for checking the entire population? Doesn't my class Quantity

    break this?
  • How to reuse constructor Quantity

    validation in web form validation? I think the validation code is duplicated since I can validate it once, or at least reuse the validation logic.
  • Since the entire value object will validate itself, does that mean I shouldn't validate anything in the Entity?
  • So how does it ShippingDate

    depend on OrderDate

    for verification, how do I check the shipping date?
  • Where does DDD factories fit into all this?
+3


source to share


3 answers


  • if any Quantity

    cannot be negative in your domain, regardless of context, it makes sense.
  • IMHO the confirmation in your constructor Quantity

    is to ensure that the application uses the class correctly. It throws an exception, these are for exceptional conditions, not the expected workflow. Thus, it has a completely different purpose than validating a web form, which ensures that the user is using your application correctly. It expects invalid input and processes it. I see no real duplication here, at least not one that could be eliminated without violating the principle of single responsibility.
  • I don't think that's the case. Do you have if(shippingDate < orderDate)

    - how did you plan to check this in value objects?
  • Ah, you see the problem. Same answer: This check applies to the Order object. Plus, you don't need to use value objects for everything. If the order date or delivery date doesn't have its own limits for each one, just keep using Date

    .
  • This seems to be a separate question, I see nothing to do with value objects.


+1


source


There are many questions, you can break them down into separate questions.

Questions 2 through 5 are highly dependent on various factors and may be subject to opinion.

But here's my answer to question 1 (and several to question 3 and 4):



The unit is responsible for its integrity. Not Aggregate Root . Each element within an aggregate can perform its own check if the aggregate remains valid as a whole.

The status check (as the correct amount or the amount that is not negative) can be done inside the appropriate class. Interdependent health checks, for example ShippingDate >= OrderDate

, can be performed at a higher level, for example. at the Aggregate root.

0


source


  • Aggregate roots imply the forced inclusion of invariants in their collection, but they do not perform the entire check. Especially not validation during construction, which is usually handled in designers or factories. In fact, moving as many (non-context-sensitive) invariants as you can to constructors and factories can be helpful. I think it is much better to have always valid objects instead of relying on method reuse ValidateThis()

    and ValidateThat()

    in the aggregated root or the entities themselves.

  • There are 3 types of validation: client-side validation, application validation (in controllers or application-level services), and domain validation (domain level). Client-side validation is required and cannot be reused. Application validation can be based on domain validation, which in your example simply means calling the count constructor and handling the exceptions it has thrown. But it can also have its own set of application-specific non-domain rules, such as checking a field password

    against it password_confirm

    .

  • In the same vein as always valid entities, value objects are best made immutable, which means you only need to validate once when you update them. However, this is an internal check, you can perfectly have a peripheral check on the containing object (for example, you cannot have more than three value objects of that type in your list, the A value object always comes with the B object value, etc.)

  • This is a situational validation, not a check on the ShippingDate internal invariant. Therefore, the Order must be responsible for verifying that, ShippingDate >= OrderDate

    regardless of the validity of each of these objects, values.

  • Factories should be used when the logic for constructing an object is complex enough, which is a responsibility in itself and as such does not fit into the object's constructor or consumer due to SRP. Factories contain design validation logic as well as constructors, making them invariant power structures as well.

0


source