How would one apply command query separation (CQS)

2019-03-08 01:31发布

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条回答
劳资没心,怎么记你
2楼-- · 2019-03-08 02:07

I'm really late to this, but there are a few more options that haven't been mentioned (though, not sure if they are really that great):

One option I haven't seen before is creating another interface for the command handler to implement. Maybe ICommandResult<TCommand, TResult> that the command handler implements. Then when the normal command runs, it sets the result on the command result and the caller then pulls out the result via the ICommandResult interface. With IoC, you can make it so it returns the same instance as the Command Handler so you can pull the result back out. Though, this might break SRP.

Another option is to have some sort of shared Store that lets you map results of commands in a way that a Query could then retrieve. For example, say your command had a bunch of information and then had an OperationId Guid or something like that. When the command finishes and gets the result, it pushes the answer either to the database with that OperationId Guid as the key or some sort of shared/static dictionary in another class. When the caller gets back control, it calls a Query to pull back based the result based on the given Guid.

The easiest answer is to just push the result on the Command itself, but that might be confusing to some people. The other option I see mentioned is events, which you can technically do, but if you are in a web environment, that makes it much more difficult to handle.

Edit

After working with this more, I ended up creating a "CommandQuery". It is a hybrid between command and query, obviously. :) If there are cases where you need this functionality, then you can use it. However, there needs to be really good reason to do so. It will NOT be repeatable and it cannot be cached, so there are differences compared to the other two.

查看更多
Lonely孤独者°
3楼-- · 2019-03-08 02:09

This question is old but has not received a satisfying answer yet, so I'll elaborate a bit on my comment from almost a year ago.

Using an event driven architecture makes a lot of sense, not only for achieving clear command/query separation, but also because it opens new architectural choices and usually fits with an asynchronous programming model (useful if you need to scale your architecture). More often than not, you will find the solution may lie in modelling your domain differently.

So let's take your purchase example. StoreService.ProcessPurchase would be a suitable command for processing a purchase. This would generate a PurchaseReceipt. This is a better way instead of returning the receipt in Order.Result. To keep things very simple, you can return the receipt from the command and violate CQRS here. If you want a cleaner separation, the command would raise a ReceiptGenerated event that you can subscribe to.

If you think about your domain, this may actually be a better model. When you're checking out at a cashier, you follow this process. Before your receipt is generated, a credit card check might be due. This is likely to take longer. In a synchronous scenario, you would wait at the cashier, unable to do anything else.

查看更多
看我几分像从前
4楼-- · 2019-03-08 02:15

CQS is mainly used when implementing Domain Driven Design, and therefore you should (as Oded also states) use an Event Driven Architecture to process the results. Your string result = order.Result; would therefore always be in an event handler, and not directly afterwards in code.

Check out this great article which shows a combination of CQS, DDD and EDA.

查看更多
登录 后发表回答