Songo
Songo

Reputation: 5726

How to utilize Value Objects validation properly when doing Domain Driven Design?

I have a simple Entity Order in pseudo code:

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 and shippingDate are all read from a web form where each is a text field that is configured by a number of validators:

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

As you can see the validation logic is duplicated between the TextField validation and the Entity validation.
I tried to introduce the concept of value object to my entity by creating Quantity class, OrderDate class and ShippingDate class. So my Order entity becomes like this instead:

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 Quantity for example will be:

class Quantity{

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

}

Now the questions:

  1. Aren't Aggregate Roots supposed to be the ones responsible for validating the entire aggregate? Isn't my Quantity class violating that?
  2. How can I reuse the validation in the constructor of Quantity in the web form validation? I think the validation code is duplicated so how can I validate it once or at least reuse the validation logic.
  3. Since all value object will be validating themselves, does that mean I shouldn't validate anything in the Entity?
  4. Since ShippingDate depends on OrderDate for validation, How should I validate the shipping date?
  5. Where do the DDD Factories fit in all of this?

Upvotes: 6

Views: 2732

Answers (3)

guillaume31
guillaume31

Reputation: 14064

  1. Aggregate roots are supposed to enforce invariants in their aggregate, but they don't do all validation. Especially not validation at construction time, which is generally handled in constructors or Factories. As a matter of fact, moving as many (non context-specific) invariants as you can to constructors and Factories can be beneficial. I think it's a much better idea to have always valid entities rather than relying on the repeated use of ValidateThis() and ValidateThat() methods on the Aggregate root or the entities themselves.

  2. There are basically 3 kinds of validation : client-side validation, application validation (in controllers or Application layer services) and domain validation (Domain layer). Client-side validation is needed and can't be reused. Application validation can rely on domain validation, which in your example means just calling the Quantity constructor and handling exceptions thrown by it. But it can also have its own set of application specific, non-domain rules - for instance, validating a password field against its password_confirm.

  3. Much in the same spirit as the always valid entities one, value objects are best made immutable, which means you only have to validate once when you new them up. However this is intrinsic validation, you can perfectly have peripheral validation in the containing entity (e.g., you can't have more than 3 value objects of that kind in your list, value object A always goes with value object B, etc.)

  4. This is situational validation, not validation of a ShippingDate-intrinsic invariant. Thus Order should be responsible for checking that ShippingDate >= OrderDate independently from the validity of each of these value objects.

  5. Factories should be used when an object's construction logic is complex enough that it is a responsibility in itself, and as such doesn't fit in the object's constructor or in the consumer, by virtue of SRP. Factories do contain construction-time validation logic, just like constructors, which makes them invariant enforcers of sorts as well.

Upvotes: 2

Dennis Traub
Dennis Traub

Reputation: 51624

These are many questions, you might want to break them down to single questions.

Questions 2 through 5 very much depend on various factors and are subject to opinion.

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

The Aggregate is responsible for its integrity. Not the Aggregate Root. Every item inside the Aggregate can do their own validation as long as the Aggregate stays valid as a whole.

State validation (as a correct amount or an amount not being negative) can be done inside the respective class. Interdependent state validation like ShippingDate >= OrderDate can be done on a higher level, e.g. in the Aggregate Root.

Upvotes: 0

Fabian Schmengler
Fabian Schmengler

Reputation: 24551

  1. if any Quantity cannot be negative in your domain, regardless of context, this makes perfect sense
  2. IMHO the validation in your Quantity constructor serves to assure the correct usage of the class by the application. It throws an exception, those are meant for exceptional states, not for expected workflow. Thus it has a totally different purpose than web form validation, which assures the correct usage of your appliation by the user. It expects invalid input and handles it. I don't see real duplication here, at least not such that could be eliminated without violating the Single Responsibility Principle.
  3. I don't think that's the case. You have if(shippingDate < orderDate) - how did you plan to validate this within the value objects?
  4. Ah, you see the problem. Same answer: this validation belongs to the Order entity. Also, you do not have to use value objects for everything. If an order date or a shipping date have no inherent constraints on each own, just keep using Date.
  5. This seems to be a separate question, I don't see any relevance to the value objects.

Upvotes: 3

Related Questions