I have a question on validation approaches in ddd. I've read quite controversial opinions on that. Some say that this should live out of entity, others say that this should be placed in entity. I'm trying to find an approach that I could follow.
As an example, suppose I have the User entity with email and password. User has a method Register(email, password). Where should the email and password validation be placed? My personal opinion is that it should be inside the Register() method. But such approach might lead to messing User class with validation stuff. One approach might be to extract the email and password rules in separate policy objects and still invoke them from Register() method.
What is your opinion on validation approaches in DDD?
Actually validity of user is context dependent. User can be valid (name and email are valid), but registration operation can be impossible to perform. Why? Because there can be already existing user with same name. So, user which looks like valid in some context, can be invalid in registration context.
So I'd moved part of validation into entities or value objects (e.g. Mail object) to avoid obviously invalid states of entities (e.g. user with null
name). But context-dependent validation should exist in that context. So, if I'm registering user:
Mail mail = new Mail(blahblahblah); // validates if blah is valid email
User user = new User(name, mail); // validates if name is valid and mail not null
// check if there already exist user with such name or email
repository.Add(user);
Also I think that method Register
should be method of some domain service (like MembershipService), because it definitely should use some repository.
First off, I think validation is somewhat of a slippery subject due in part to the various contexts within which it needs to be considered. Ultimately, there will be validation rules executed at various application layers. At the very least, there should be standard guards in domain objects. These are just regular pre-condition and argument checks that should be part of any well designed object and fits with your opinion for the Reigster
method. As stated by lazyberezovsky, this is to prevent the objects from getting into an invalid state. I side with the always-valid school of though on this. I think if there is a need to persist entities in an invalid state, a new entities should be created for this purpose.
One issue with this approach alone however is that there is often a need to export these validation rules to other layers, such as to a presentation layer. Moreover, in the presentation layer, the rules need to be formatted differently. They need to be presented all at once and potentially translated into another language, such as JavaScript for immediate client-side feedback. It can be difficult or impractical to attempt to extract validation rules from exceptions raised by a class. Alternatively, the validation rules can be re-created at the presentation layer. This is much simpler and although potentially violating DRY it allows for the rules to be context dependent. A specific workflow may require for different validation rules than the ones enforced by the entity itself.
The other issue with the described approach is that there may be validation rules which exist outside of the scope of an entity, and these rules must be consolidated together with the other rules. For example, for user registration, another rule is to ensure that the email address is unique. The application service hosting the applicable use case would usually enforce this rule. However, it must also be capable of exporting this rule to other layers, such as presentation.
Overall, I try to place as many constraint checks into the entities themselves because I think entities should be always-valid. Sometimes it is possible to design the rule framework such that it can be used to both raise exceptions and be exportable to outer layers. Other times it is easier to simply replicate the rules across layers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With