This post has been sitting as a draft for a while and finally thought I should post it in response to the recent thread in the ALT.NET forum Validate Value Objects.
So here goes, I'm going to explain basically the approach to validation that I prefer, or at least the approach we used on my last DDD project.
If you want to do validation within your domain you have a couple of choices:
- Entity or Service Based - Some people move their business logic, including validation, into the services and leave the entities as DTO's. This brings an anaemic domain model. An alternative is to make each entity responsible for some of its own validation.
- Attributes or Rule Classes - If you want to use attributes then you'll likely be looking at something like EViL or the Validation Application Block (or an example project like Xeva). Attributes work for simple rules, but don't handle complex or state/processed based validation well. For more complex scenarios you'll likely turn to little rule classes (which I choose to see as variations on the SPECIFICATION pattern). Some people combine attributes and custom classes but personally I prefer just to use rule classes for all cases, it does mean you can't generate the GUI validation (client side validation) but I've yet to find an approach to automatically generating client side validation that I liked so in my view if you want that validation in the GUI then you should consider writing it separately.
- Inject or Direct Dependency - Some people who use rule classes inject the rules into the ENTITIES. Injecting the rules adds flexibility, but just having the domain decide which rules to use is going to be enough in a lot of cases. You sometimes need to inject though and Udi Dahan's post on Generic Validation has some good points on where it does fall down, but I still think ENTITIES can handle a lot of their own validation.
- Immediate or Delayed - Its natural to assume that the best way to validate is to throw exception from the property setters, disallowing invalid states. This doesn't work for cross field validation or cross object validation or where the object will be temporarily invalid. If you want a good discussion of this see this book.
- Notification or Event - Udi Dahan describes an event based notification pattern.
- Constructor - The more you validate in the constructor the better (Constructor Initialization) but it does complicate the use of the domain objects and it is only ever a partial answer. For example if an entity moves through multiple states, or is used in multiple processes then whether it is valid is contextual. I tend to use constructor arguments for immutable's, for example a business key.
So what do we use:
- Entity Based - Services are certainly involved in validation but an ENTITY is responsible for its own validation and an AGGREGATE root is responsible for validating that entity AGGREGATE.
- Rule Classes - We evaluated attributes but they only handle very simple validation (not null, max length) well and we didn't want to have two types of validation at play so we just use rule objects.
- Direct Dependency - For the sorts of systems I am developing injecting rules is not necessary, I have no need to decouple an entity from its own validation rules or to vary the rules at run-time.
- Delayed - You can ask an ENTITY whether it is valid at any time and you can temporarily leave it invalid, this lets the higher layers manipulate the objects as they see fit but lets us ensure that once an application "transaction" is complete the object is back to being valid.
- Notification - We use a Notification style approach and a style where you can ask an object whether it is valid/ready to do something (and get back the notification) or you can just try to do it(which may then cause you to get an exception containing all the reasons that the operation is not possible).
The implementation is simple but varies from case by case.
For simple cases an entity will have a GetBrokenRules method. This method will create a collection of IDomainRule objects, it then forces each rule to evaluate the object in question. When a rule fails a description of the failure is put into a separate collection of BrokenDomainRule (NOTIFICATION) objects that the GetBrokenRules method returns.
For more complex cases we have to vary the validation based on the state (as in state pattern) of the object. This doesn't really change things too much though but instead of calling GetBrokenRules you call GetBrokenRulesDisallowingTransitionTo(...) and pass in the representation of the new state (maybe an enum value).
An AGGREGATE root is responsible for coordinating the validation for the entire AGGREGATE, so if you ask a Customer to validate itself then it will validate the entire aggregate and return any failings.
If a rule involves more than one AGGREGATE then it should be performed in a SERVICE. For example if we had this requirement:
Before moving a Customer to the Active state you want to ensure that the Customer itself is valid and also that it has at least one Account.
You could make the Customer responsible for this sort of validation but a better option is to move this validation into a separate SERVICE. The same goes for validation that involves multiple instances of the same class (e.g. only one Customer with a specific e-mail address).
In addition if validation requires you to call to an external resource, even a REPOSITORY, then I would move it into a SERVICE and the SERVICE coordinates the validation.
As well as cross-aggregate validation SERVICES should handle any process specific validation.
Collections can manage some validation to do with associations. For example if we had this requirement:
A Customer can only have one earnings Portfolio
We've found the best way to handle this is to make the appropriate collection responsible for the validation so when you call customer.Accounts.Add(account) you get an exception if for any reason the addition is not possible (the exception tells you all the reasons it was impossible). You also have a CanAdd method so you can evaluate without raising an exception (wouldn't work if we had to worry about the effects of threads on this code).
This validation is not performed when you validate the AGGREGATE that owns the collection (Customer) because the methods that control adding/removing from the collection can ensure the collection is always valid.
Some validation only needs to be performed on creation. If this validation is complex enough then it can be worth moving it into a FACTORY, in particular if you want to perform the validation before creating the associated ENTITY.