Domain Model Validation
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.
Value Objects
Entities
Choices
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).
Implementation
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).
Aggregates
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.
Cross Aggregate
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.
Services
As well as cross-aggregate validation SERVICES should handle any process specific validation.
Associations
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.
Factories
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.
Excellent information!
ReplyDeleteI have a question regarding the process that happens when calling the IsValid() method on an entity that contains a collection of other entities. Will this call result in looping thru and calling IsValid on all of the collection entities?
Or is the entity only worried about checking the validity of itself?
Probably a stupid question but I could see a lot of traversing happening if you have a large tree of entities and all of their IsValid methods get called.
This question is relevant to your Notifications as well.
Informative post, thanks!
ReplyDeleteHow do you approach repositories. One repository implementation that offers the same features for any entity? Such as
NHRepository<Customer>.Find(blah)
Or, do you write specialised repositories for each aggregate root?
Does your repository implement an interface, so that you can also create counterpart repositories for in-memory testing?
Sorry for the barrage of question :)
I'm also interested on how you use repositories! ( see Tobins question)
ReplyDeleteSeems like Jeremy Miller has moved over to the "Save<T>(T)" way of doing it.
http://codebetter.com/blogs/jeremy.miller/archive/2008/07/09/classes-that-show-up-in-every-project.aspx
Given that you validate your value objects in the constructor do you think that object initializers should be avoided?
@Tobin
ReplyDeleteNot at all on the questions, good to see people are reading :)
I'm currently using the generic Repository approach on an Active Record project but I wouldn't use it on a DDD project.
For me its (at most) one repository per aggregate and I'd keep custom queries in that repository (or in passed in Linq specifications). I don't particularly like the generic Repository approach in a DDD sense as it doesn't seem in keeping with DDD, means that queries are moving out (usually as Linq) and doesn't address cases where you need to use SQL or even handle objects from outside the DB.
They do indeed implement an interface and I use base classes to take care of a lot of the work of implementing the repositories, actually I think I might come up with an example of this at some stage.
@Gary
ReplyDeleteThanks :)
On entity validation, for me it would depend on the situation.
When we validate an AGGREGATE root we only validate that aggregate, so if Customer and Order are both aggregate roots then validating the Customer would not validate the Orders. If we need to validate a Customer and its Orders together then we have to do that in a domain service.
However if Order is the root of an aggregate containing OrderLines then validating an Order would validate the OrderLines too.
Definitely don't think its a stupid question, but my view is you need to enforce the invariants at the aggregate level and if that means loading extra data in then thats the way its got to be. If its a problem then you can deal with it but I wouldn't assume it will be.
Does that answer your question?
@Andreas
ReplyDeleteHope answers to Tobin answer your questions on the way I use repositories, if not give me a shout.
I must admit that so far object initializers are not something I've used a lot, mainly because in tests I use a test data builder to create VALUE OBJECTS. These builders give me the advantages of the initializer without the issues.
It is more work but I far prefer the result. For example the builders bring is that they can populate the VALUE OBJECT with default values, so if you create an Address using one then it can ensure that the Address gets a post code even if you don't specify one.
Thanks for your feedback Colin!
ReplyDeleteI'm still trying to grasp many DDD concepts so at times things are a little slow going.
With help from people like you my knowledge is definitely expanding.
Excellent post Colin!
ReplyDeleteI'd just like to add one thing: the attribute approach is still valid (at least, I'm still using it to simulate some sort of design by contract). In fact, I'm using the attribute to ensure that domain rules are correctly filled up (it was that or having to add some code to ensure that each rule is corecctly built). Thoughts on this idea?
Hi,
ReplyDeleteIn relation to :
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.
How do you actually implement this? Do you keep track of all aggregates that have been altered in your unit of work somehow and validate each of them prior to committing or rollback?