Business rule validators and command handler in CQRS
Asked Answered
M

1

36

I am new to CQRS 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.

  1. Create instances of two command validators in the command handler.
  2. First one validates the command data same as client side validation (required field, valid email, etc.)
  3. 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.
  4. We look at the ValidationResult returned by the Validator.Validate(ICommand cmd) and we find out there are errors
  5. 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

Milklivered answered 22/3, 2011 at 22:33 Comment(7)
All* your validation should be done prior to the command, when you issue the command your making the assumption that there is no reason the command should fail.Does
In the controller of the web application, before I send the command to the bus? If so, how? Calling a validator service or something to that effect?Milklivered
Pretty much. Run it through your validator, if it's ok, pass it onto the bus, otherwise show the user an error(s).Does
Simpler than I had originally thought :) thanksMilklivered
@Phill: How will you validate that another conflicting modification has not occurred to the aggregate prior to sending a command?Coefficient
@qes - Can you give an example? It would be very rare for 2 messages to be sent at the same time. With CQRS you're dealing with specific services, like a OrderService, you're not going to have two different messages sent at the same time for the same order that 1 can invalidate the other. If you're updating customer information, I'm not sure when two things would be updating the same customer information in a CustomerCareService...Does
@qes - how do you communicate the broken business rule within the domain to the user? you said you throw exception but what happens next?Milklivered
C
44

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.

Coefficient answered 23/3, 2011 at 5:40 Comment(6)
How do you communicate the errors of command rejection to the user? Email, callback with request id, etc?Milklivered
@gandalf: I suppose that would depend on the kind of client. If you have a WPF client then a callback would probably be the easiest. If you have an MVC client then I'm not sure... Maybe you can pass 2 URLs in your command: one to GET in case of success, the other to GET in case of failure. The service can then GET the appropriate URL and you can execute the proper logic client-side.Superincumbent
+1 as to what qes said. The only thing I don't agree on is putting validation logic inside the domain, even if you have multiple front-ends. At the end of the day command handlers are just as much part of your domain (or at least the only gateway to). But don't take my word for it: tinyurl.com/66kaorvMegan
There may be specifications coming from the domaind and specifications or contraints coming from other areas. Isn't this a matter of trust. Where are the trust boundaries? As the domain might be used the wrong way and migh be reused in different applications I think there is a trust boundary around the domain model. Hence I think validation of domain specifications belong into the domain. Validation of specifications for other reasons (e.g. persistence) belong in the corresponding layer, because there is another trust boundary protecting it.Sciatic
Just in case anyone finds this useful I've gone into more depth regarding command validation in a CQRS system. You can find it here: How to Validate Commands in a CQRS ApplicationPinkston
@Codescribler, could you have a look at my question here: softwareengineering.stackexchange.com/questions/372338/… ? It is similar to this one.Groff

© 2022 - 2024 — McMap. All rights reserved.