I have a use case to make updates to a slightly complex JSON object and then return the list of changes made (not the updated data). The process of finding the list of changes is extremely similar to the process of making the changes and it would bring in some redundancy if I chose to separate the two methods. So I'm wondering if there is a way to do this without choosing DRY over CQS and SRP.

  • 1
    You can have separate commands (update and read), which will use same process/method under the hood. – Fabio Jun 24 '19 at 6:09
  • 1
    @Fabio, to my mind that just shows the total insanity of CQS. Just have one call that does the update and returns the list of changes and move on with life. If that violates CQS, then CQS is at fault. – David Arno Jun 24 '19 at 7:09
  • If those processes are that similar you can come up with some generic utility method and extract it to the infrastructure layer. – Bohdan Stupak Jun 24 '19 at 8:37

Not all Command Query Separation (CQS) violations are created equal.

A famous example is popping a stack. The existence of a pop command that mutates a stack while returning what was popped violates the clear separation between command and query. However, while many argue that to follow CQS, pop can't return, I'll argue that there is a deeper violation here. One that can be mitigated without messing with pop.

The real problem is if I have to write code that pops the stack and then pushes what I popped right back on the stack. You can't fix that by changing pop. You fix that by creating peek.

So long as peek exists the most critical thing CQS asks for isn't being violated. Which is: a way to query without mutating state. If peek gives you exactly what pop gives you then that most critical part of CQS hasn't been violated.

Purists will complain that pop still shouldn't return under CQS but I'm not one of them. So long as peek is reasonably easy to find in the documentation of pop this shouldn't cause problems. This is how I prefer to follow CQS. Never force users to mutate to query. Following that has a big payoff. Never allowing a command to double as a query only pays off because it forces people to go find the query and use that. Meh. Either way, give me peek.

Now all that said, who said you even had to return?

What I see you doing is parsing and mutating a JSON data structure while logging your activity. Do that and you aint returning a thing. You've just expanded what's being mutated. Simple.

There would be no DRY violation here because we didn't duplicate parsing code for finding and making changes. We just made the code say more when run.

Don't want to put it in a log file? Need to show it to the user? Fine, pass the algorithm some way to send output to the user. Use it the same way you'd use a log. The algorithm doesn't have to know which it's talking to. It just needs an interface to talk through as it declares changes are being made. That separation respects SRP.

None of this involves creating a separate process for finding the list of changes and the process of making the changes. It's just about what you change.

You can take this even further and fold both the JSON and the changes together so that you can build the JSON from the record of every change ever made to it. That's not a new idea. Its how git works.

And even if you were forced to separate the process of finding the list of changes and the process of making changes that still doesn't force you to violate DRY. The process of making changes needs to know where those changes should be. The process of finding them does as well. So make one method that finds the changes and two others: one that makes them and one that records them.

Do that and it's easy to write a query that finds where changes would be made without making them because you separated finding and making. Just plug in changing code that makes no changes and you have a query that doesn't mutate.

Now sometimes DRY needs a little violating. x = 3; y = 3; might seem repetitive but x and y mean different things. I don't care that right now they happen to have the same value. I'm keeping them separate so future programmers can change them independently. It's the same with repetitive code. Doesn't matter if every keystroke is the same. What matters is if that identical code exists so it can be changed independently. This is only a problem if a change in one always implies a change in the other.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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