How would one apply command query separation (CQS), when result data is needed from a command?

Viewed 20315

In wikipedia's definition of command query separation, it is stated that

More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.

If I am issuing a command, how should I determine or report whether that command was successful, since by this definition the function cannot return data?

For example:

string result = _storeService.PurchaseItem(buyer, item);

This call has both a command and query in it, but the query portion is result of the command. I guess I could refactor this using the command pattern, like so:

PurchaseOrder order = CreateNewOrder(buyer, item);
_storeService.PerformPurchase(order);
string result = order.Result;

But this seems like it's increasing the size and complexity of the code, which is not a very positive direction to refactor towards.

Can someone give me a better way to achieve command-query separation when you need the result of an operation?

Am I missing something here?

Thanks!

Notes: Martin Fowler has this to say about the limits of cqs CommandQuerySeparation:

Meyer likes to use command-query separation absolutely, but there are exceptions. Popping a stack is a good example of a modifier that modifies state. Meyer correctly says that you can avoid having this method, but it is a useful idiom. So I prefer to follow this principle when I can, but I'm prepared to break it to get my pop.

From his view, it's almost always worth it to refactor towards command/query separation, except for a few minor simple exceptions.

9 Answers

Take some more time to think about WHY you want Command Query Separation.

"It lets you use queries at will without any worry of changing system state."

So it is OKAY to return a value from a command to let the caller know it succeeded because it would be wasteful to create a separate query for the sole purpose of finding out if a previous command worked properly. Something like this is okay in my books:

boolean purchaseSucceeded = _storeService.PurchaseItem(buyer, item);

A disadvantage of your example is that it is not obvious what is returned by your method.

string result = _storeService.PurchaseItem(buyer, item);

It is not clear what 'result' is exactly.

Using CQS (Command Query Separation) allows you to make things more obvious, similar to below:

if(_storeService.PurchaseItem(buyer, item)){

    String receipt = _storeService.getLastPurchaseReceipt(buyer);
}

Yes, this is more code, but it is more clear what is happening.

Oh that's interesting. Probably I have something to say, too.

During recent time I've been using non-orthodox CQS (maybe not CQS at all for somebody, but I don't really care) approach which helps to avoid messy Repository (because who uses the specification pattern, huh?) implementations and Service layer classes which grow up absolutely enormously over time, especially in huge projects. The problem is it happens even if everything else is fine and developers are pretty skilled, because (surprise) if you have a big class it doesn't always mean it violates SRP in the first place. And the common approach I see in such projects very often is "Oh, we've got huge classes, let's divide them", and that division is mostly synthetic rather than evolving naturally. So, what do people do to cope with this? They make several classes out of one. But what happens with DI in a huge project when you suddenly have several times more classes than before? Not really nice picture since DI is probably already pretty loaded with injections. So there come workarounds as facade pattern etc. (when applicable), and the implications are that we: don't prevent the problem; deal with consequences only and spend much time for it; often use "synthetic" approach to refactoring; get less evil instead of more evil, but still that's evil.

What do we do instead? We apply KISS and YAGNI to CQS as a first step.

  1. Use Commands/CommandHandlers and Queries/QueryHandlers.
  2. Use generic return object for both queries and commands which contains result and error (ouch!).
  3. Avoid standard service and repository implementations by default - only if it's strictly necessary.

What problems are solved with this approach?

  1. Early prevention of code mess, much easier to work with and scale (future proof).
  2. Believe it or not, for medium size project we had neither service classes nor repositories at all. The bigger the project, the more beneficial such approach is (if we assume that CQRS and ES aren't needed and compare only to standard service + data layers). And we're extremely happy with it since it's more than enough for most medium-sized projects in terms of costs and efficiency.

So what would I suggest you to do?

  1. Use right tool for the right job. Use the approach which solves your problems and avoid doing everything by the book if it comes with unnecessary complexity for your case "just because that's why". How often do you see fully RESTful Level 3 APIs, by the way?..
  2. Don't use anything if you don't need it and especially if you don't understand it since if you really don't, it will do more harm than good. CQRS is good for some cases and is still pretty easy to understand, but comes at a price of development and support; ES is rather difficult to understand and even more difficult to build and support.

Well, this is a pretty old question but I post this just for the record. Whenever you use an event, you can instead use a delegate. Use events if you have many interested parties, otherwise use a delegate in a callback style:

void CreateNewOrder(Customer buyer, Product item, Action<Order> onOrderCreated)

you can also have a block for the case where the operation failed

void CreateNewOrder(Customer buyer, Product item, Action<Order> onOrderCreated, Action<string> onOrderCreationFailed)

This decrease the cyclomatic complexity on the client code

CreateNewOrder(buyer: new Person(), item: new Product(), 
              onOrderCreated: order=> {...},
              onOrderCreationFailed: error => {...});

Hope this helps any lost soul out there...

Related