2

We're currently exploring CQRS in our future applications. To give a bit of a background, we use a simple CRUD-style service before something like this.

Old Pattern:

  1. Controller
  2. Command/Service
  3. Model

Then the validation logic is inside the model like ValidateSave(SalesOrderModel), ValidateSubmit(SalesOrderModel), ValidateApprove(SalesOrderModel), etc. Since it's validating the SalesOrderModel, it's very easy to reuse a lot of the validation logic and sometimes we straight up do something like this bool ValidateSubmit(SalesOrderModel model) => return ValidateSave(model)

Now that we're in CQRS the layout is something like this:

  1. Controller
  2. CommandRequest
  3. CommandHandler
  4. Entity/Model <-- POCO auto-generated by ORM

Then the common practice is to validate it against the CommandRequest which in this case will be SaveSalesOrderCommand, SubmitSalesOrderCommand, etc. Now the problem here is they are of different class so I have to separately code each of their validation logic. I find this hard to maintain since I always end up copy pasting my Save Validation to Submit Validation whenever a new validation comes up. Not to mention the other issues that rises up with non-DRY approach.

I can't seem to find any resources online that tackles this problem and I'm starting to think it's acceptable and it's a "me" issue. The validation is just one example, this is also applicable in Handler side where sometimes there are repetitive code in which in our previous pattern, we either do it in the Command or Model (so data to process is already mapped to Model). While here in CQRS, they seem to be vertically sliced to the point that a command request from a same module is completely separate from each other code-wise and file-wise.

My idea to solve this:

  1. Create an interface for the CommandRequest like ISalesOrderCommand which will be implemented by SaveSalesOrderCommand, SubmitSalesOrderCommand, etc. Then a SalesOrderValidation where all validation logic is placed and validates against SalesOrderCommand. I tried this and it works but I don't really prefer this mostly due to the added complexity and forcing other CommandRequest to implement fields that they don't really need.

  2. Similar to #1 but in this case, the SalesOrderValidation would validate against the entity SalesOrder. Then on each validation of the CommandRequest, I will map them to the SalesOrder entity. I like this and a lot simpler that #1. I just don't know if this has negative implications that I'm still not seeing.

Note: For simplicity sake, we don't implement a full-blown DDD design so the entity is just a POCO and not some entity with rich business implementations attached to it.

Thanks!

4
  • 1
    Could you explain why Save, Submit and Approve command payloads all need to contain the full SalesOrder? Usually, validating a command is rarely the same as validating an entity, as a very limited number of commands should contain the full entity. An alternative is to let the domain layer validate the entity. Commented Jun 27, 2023 at 7:46
  • @guillaume31 For the Approve, yes, it doesn't necessarily need the full details. For Save and Submit it needs the full details since in our scenario, these actions are initiated right after the user encode/edit the form (not yet persisted in db). So a user can edit a form and click Save if it's not yet final or Submit if what they edited is final. Both of these actions would still have to persist the updated record in database and have a high probability of having same validation.
    – Lawrence
    Commented Jun 27, 2023 at 10:22
  • I would agree that the domain layer is the best place for this but if we go this route, this would mean we'll maintain yet another set of POCOs aside from auto-generated entities of EF and command. There's a lot of mapping again. A rich domain layer would really solve most of these but we're looking for alternatives as much as possible to limit overengineering some of our simple applications.
    – Lawrence
    Commented Jun 27, 2023 at 10:29
  • 1
    It is unclear to me why you don't just add a reusable component that both the Save and Submit command handlers depend on to perform the same validation. Did you miss this as an option or is there a reason you're not taking this route?
    – Flater
    Commented Jul 27, 2023 at 0:20

2 Answers 2

1

Stating the bleeding obvious here: promoting reusability entails introducing an additional abstraction which allows for said reuse.

Less obvious is that there's a difference between theoretical elegance and practical effort. Yeah, reusability would probably apply to some of these fields. But we have to weigh it against the cost of introducing reusability in a concept that generally is built on the supposition that commands are designed as independent entities. Once you tie them together, you open the door to (changes to) one command breaking the other, or alternatively to paint yourself in a corner of your reusable logic needing to cater to slightly different needs between them in the future.

Interestingly, I think you're dealing with one of both options here. From your comment:

For the Approve, yes, it doesn't necessarily need the full details.

This strongly suggests that you need to define the approval command as its separate thing, since it makes no sense to have this include fields (nor any related types that comprise these fields) that it simply does not care about. Tying this to the other commands makes no sense, the drawbacks outweigh the benefits.

However, you have a relatively strong case that the models for the Save/Submit steps need to be identical, since the only difference between them is what you subsequently do with the data, not the a distinction in the data itself. Submit having data that Save doesn't would infringe on the purpose of having the Save command in the first place. Save having data that Submit does not have makes it inherently irrelevant as you'd never use that saved data to eventually submit it.

In saying that, there are edge cases to this. For example, you might not allow certain data to be saved in advance and only to be supplied "live", so to speak. Similarly, you might allow for a "notepad" field to be stored in a save model, which won't be used in the actual submission and is only being used as a reminder for the people who are step-by-step building the form before its eventual submissions.

Considering those edge cases means that we're dealing with partially similar models, not fully identical ones. The consequence here is that you can't just build one reusable component to handle both of them, you need three: one for A, one for B, and one for the A/B similarities. That's a taller order, which costs more effort and complexity, and I'd start arguing that unless there is particularly unique logic here (i.e. complex business validation, not just plain validation for empty strings etc), I'd lean away from reusability here.

What I'm saying here is that you can argue this either way, and I don't think there's a clear winner here based on what you've presented.

I get the feeling that your question might be rooted in a near-dogmatic application of reusability. Your mention of "code repitition" suggests it, in my opinion; as does the fact that your question spends no time justifying the concept of reuse, merely pointing out that it could be reused and therefore that it should. Or, to put it differently:

enter image description here

Admittedly, this is inference on my part, but in my defense I have spent years identifying these kinds of developer behaviors and addressing them as part of a team's dev culture, which is why I'm trying to offer that here as well. The main thing I point out in these cases is:

Just because it's identical does not mean it's the same.

What I mean by that is that just because two things are exactly the same right now, does not inherently mean that they share the same lifecycle. When deciding whether you should merge two identical items into one, you have to ask yourself the question:

If I need to change one of them in the future, am I absolutely sure that the other one needs to be changed in an identical way, in every case?

I am not across your entire set of business requirements, so I can't provide a final judgment on this. All I can say is that in my interpretation of your question you appear to be personally biased towards implementing reusability just because you can, so I would suggest you re-evaluate if this is actually the right path or if you're taking it because you're used to taking it.

1
  • Hi Flater! It's been more than a year and we've proceeded what we have and just see through things as we go. I definitely see the practicality of having duplicate code and not forcing reusability between 'somewhat similar' commands which is what we are doing right now. I don't say its the perfect way since you'll of course suffer through the cons of non-DRY when adding logic. We then had a practice to help us bridge this gap which is to create a reusable service as needed. Mostly for business logic and so far never for validation. It'll be there more naturally instead of forced.
    – Lawrence
    Commented yesterday
0

Indeed, your problem is a common one, as the CQRS pattern by design encourages separation of commands and queries and their associated logic. Therefore, it may naturally lead to some code duplication. It's not a "you" issue, it's a system architecture design challenge that many developers face.

You've proposed two solutions and each has its own trade-offs:

1. Using interfaces (ISalesOrderCommand):

This would indeed allow you to share validation logic between command classes. However, as you pointed out, you'd be enforcing all command classes to have the same structure, even if they don't need all the fields. This could lead to confusion for other developers and even introduce bugs if fields are mistakenly used.

2. Validating against the SalesOrder entity:

This would avoid the problem of unnecessary fields in command classes. However, it would mean that your command classes would have to be mapped to the entity before validation, which might not always be desired or possible. Also, it would tightly couple the command validation with your domain model structure which can make your system less flexible to changes.

Another solution would be to introduce a new layer in your architecture: The Application Service layer. The Application Service layer would sit between the Controller and the CommandHandler. It could have methods such as SaveSalesOrder(SaveSalesOrderCommand command), SubmitSalesOrder(SubmitSalesOrderCommand command), etc. Inside these methods, you could perform validation that is common to all methods. Each individual command handler could then handle validation specific to that command. This would help reduce code duplication while still keeping your commands and their associated validation logic separate.

Consider the following structure:

Controller
Application Service
CommandRequest
CommandHandler
Entity/Model <-- POCO auto-generated by ORM

Another approach you might consider is to use Decorators or Middleware to handle validation logic that is common across commands. This would allow you to reuse validation logic without tightly coupling it to specific commands or entities. It also allows the validation logic to be modular and pluggable.

Lastly, you might consider using a validation library or framework that supports reusable and composable validation rules. This can help avoid writing duplicate validation code. FluentValidation, for example, is a popular library in the .NET ecosystem that enables this.

Remember, there is no one-size-fits-all solution to this problem. The best approach depends on the specifics of your project, your team's preferences, and the trade-offs you're willing to accept.

1
  • Thanks for these options! We currently implement something similar to what you have explained. We useIPipelineBehavior for validators so they are called automatically before invoking the command. We then create a SalesOrderCommonValidator using FluentValidation and these are called by SaveSalesOrderValidator and SubmitSalesOrderValidator. It's limitation though is we can only pass the primitive datatypes of command for validation. This means that both SaveSalesOrderValidator and SubmitSalesOderValidator still have to know the sequence of validation to call.
    – Lawrence
    Commented Jun 27, 2023 at 10:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.