I am using FluentValidation to validate my service operations. My code looks like:
using FluentValidation; IUserService { void Add(User user); } UserService : IUserService { public void Add(User user) { new UserValidator().ValidateAndThrow(user); userRepository.Save(user); } }
UserValidator implements FluentValidation.AbstractValidator.
DDD says that domain layer have to be technology independent.
What I am doing is using a validation framework instead of custom exceptions.
It's a bad idea to put validation framework in the domain layer?
Well, I see a few problems with your design even if you shield your domain from the framework by declaring an IUserValidator
interface.
At first, it seems like if that would lead to the same abstraction strategy as for the Repository and other infrastructure concerns, but there's a huge difference in my opinion.
When using repository.save(...)
, you actually do not care at all of the implementation from the domain perspective, because how to persist things is not a domain concern.
However, invariant enforcement is a domain concern and you shouldn't have to dig into infrastructure details (the UserValidtor
can now be seen as such) to see what they consist of and that's basically what you will end up doing if you do down that path since the rules would be expressed in the framework terms and would live outside the domain.
Why would it live outside?
domain -> IUserRepository infrastructure -> HibernateUserRepository domain -> IUserValidator infrastructure -> FluentUserValidator
Perhaps there's a more fundamental issue with your design and that you wouldn't even be asking that question if you adhered to that school of though: always-valid entities.
From that point of view, invariant enforcement is the responsibility of the domain entity itself and therefore shouldn't even be able to exist without being valid. Therefore, invariant rules are simply expressed as contracts and exceptions are thrown when these are violated.
The reasoning behind this is that a lot of bugs comes from the fact that objects are in a state they should never have been. To expose an example I've read from Greg Young:
Let's propose we now have a
SendUserCreationEmailService
that takes aUserProfile
... how can we rationalize in that service thatName
is notnull
? Do we check it again? Or more likely ... you just don't bother to check and "hope for the best" you hope that someone bothered to validate it before sending it to you. Of course using TDD one of the first tests we should be writing is that if I send a customer with anull
name that it should raise an error. But once we start writing these kinds of tests over and over again we realize ... "wait if we never allowed name to become null we wouldn't have all of these tests" - Greg Young commenting on http://jeffreypalermo.com/blog/the-fallacy-of-the-always-valid-entity/
Now don't get me wrong, obviously you cannot enforce all validation rules that way, since some rules are specific to certain business operations which prohibits that approach (e.g. saving draft copies of an entity), but these rules aren't to be viewed the same way as invariant enforcement, which are rules that applies in every scenarios (e.g. a customer must have a name).
If we now look at your code and try to apply the always-valid approach, we clearly see that the UserValidator
object doesn't have it's place.
UserService : IUserService { public void Add(User user) { //We couldn't even make it that far with an invalid User new UserValidator().ValidateAndThrow(user); userRepository.Save(user); } }
Therefore, there's no place for FluentValidation in the domain at this point. If you still aren't convinced, ask yourself how you would integrate value objects? Will you have a UsernameValidator
to validate a Username
value object everytime it's instanciated? Clearly, that doesn't make any sense and the use of value objects would be quite hard to integrate with the non always-valid approach.
That's actually something I struggled with and I've been asking that myself for a while (and I'm still not entirely convinced about what I'll be saying).
Basically, what I've come to understand is that it isn't the job of the domain to collect and return errors, that's a UI concern. If invalid data make it's way up to the domain, it just throws on you.
Therefore, frameworks like FluentValidation will find their natural home in the UI and will be validating view models rather than domain entities.
I know, that seems hard to accept that there will be some level of duplication, but this is mainly because you are probably a full-stack developer like me that deals with the UI and the domain when in fact those can and should probably be viewed as entirely different projects. Also, just like the view model and the domain model, view model validation and domain validation might be similar but serves a different purpose.
Also, if you're still concerned about being DRY, someone once told me that code reuse is also "coupling" and I think that fact is particularly important here.
I will not re-explain those here, but there are various approaches to deal with deferred validations in the domain such as the Specification pattern and the Deferred Validation approach described by Ward Cunningham in his Checks pattern language. If you have the Implementing Domain-Driven Design book by Vaughn Vernon, you can also read from pages 208-215.
Validation is an extremely hard subject and the proof is that as of today people still don't agree on how it should be done. There are so many factors, but at the end what you want is a solution that is practical, maintainable and expressive. You cannot always be a purist and must accept the fact that some rules will be broken (e.g you might have to leak some unobtrusive persistence details in an entity in order to use your ORM of choice).
Therefore, if you think that you can live with the fact that some FluentValidation details makes it to your domain and that it's more practical like that, well I can't really tell if it will do more harm than good in the long run but I wouldn't.
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