I am new to C开发者_高级运维QRS and I am tying to make sense of business rule validation within the write side (domain). I know that client side validation should be done in terms of valid date (required field, string length, valid email, etc) and business rule/business domain related validation should be done in domain side. Actually, same client side validation rules should also be applied to command in the domain since we don't trust the users.
So, we have a valid command (AddEmailToCustomer) and the command handler is invoked on the command. Here is my approach to validation.
- Create instances of two command validators in the command handler.
- First one validates the command data same as client side validation (required field, valid email, etc.)
- Second validator validates the data based on logic within the second validator. Something like "is this customer active", or what ever. I know the changing email doesn't fit here but it is not important. Important thing is there is a business validation here.
- We look at the ValidationResult returned by the Validator.Validate(ICommand cmd) and we find out there are errors
- We will not get a customer from repository to call on the UpdateEmail method on the AR. So what do we do at this point?
Do I throw and exception in the command handler and add these errors there? Do I send the command to error queue or somewhere else? Do I respond with something like Bus.Reply and return error code? If so, what do I do with the error messages? How do I communicate these errors to the user? I know I can email them later but in a web scenario I can send a request id in the command (or use the message id), and poll for response with the request id and display the error messages to user.
Your guidance is appreciated.
Thanks
It's important to know that commands can be rejected after they are sent to the handler.
At the very least, you could encounter a concurrency violation that cannot be detected until the aggregate root is touched.
But also, the validation that can occur outside of the entity is simple validation. Not only string lengths, numeric ranges, regex matching, etc. but also validation that can be reasonably satisfied through a query or view, like uniqueness in a collection. It's important to remember that validation involving a materialized view is likely to be eventually consistent, which is another reason a command could be rejected from the aggregate, inside the command handler. That said, to preempt the situation, I often use read models to drive UI choices which only allow valid actions.
Validation that cannot occur outside an entity is your business logic validation. This validation is dependent upon the context in which it is run (see Udi Dahan's Clarified CQRS).
Business logic should not be in a separate validation service. It should be in your domain.
Also, I'm of the opinion that validation which occurs in the UI should be re-checked not in the command handler, but in the domain too. That validation is there to prevent corruption to the domain -- if it is not performed outside the domain then the domain is still subject to invalid parameters.
Using command handlers to duplicate this validation is only a convention. If no other front end is sending commands, then it is a useless duplicate. If there are multiple front ends, it is just one choice of where to place the then-necessary duplicate validation, and in those cases I prefer to handle it in the domain.
Lastly, you will need to bubble up commands rejected from within the handler. I accomplish this with exceptions as much as possible.
精彩评论