Reputation: 5726
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:
Quantity
class violating that?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.ShippingDate
depends on OrderDate
for validation, How should I validate the shipping date?Upvotes: 6
Views: 2732
Reputation: 14064
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.
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
.
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.)
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.
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
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
Reputation: 24551
Quantity
cannot be negative in your domain, regardless of context, this makes perfect senseQuantity
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.if(shippingDate < orderDate)
- how did you plan to validate this within the value objects?Date
.Upvotes: 3