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

后端 未结 10 1979
慢半拍i
慢半拍i 2021-01-30 06:25

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

More formally, methods should return a value only if they are referentially t

相关标签:
10条回答
  • 2021-01-30 06:48

    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 Seperation) allows you to make things more obvious

    similar to below:

    if(_storeService.PurchaseItem(buyer, item)){
    
        String receipt = _storeService.getLastPurchaseReciept(buyer);
    }
    

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

    0 讨论(0)
  • 2021-01-30 06:52

    I like the event driven architecture suggestions other people have given, but I just want to throw in another point of view. Maybe you need to look at why you're actually returning data from your command. Do you actually need the result out of it, or could you get away with throwing an exception if it fails?

    I'm not saying this as a universal solution, but switching to a stronger "exception on failure" instead of "send back a response" model helped me a lot in making the separation actually work in my own code. Of course, then you end up having to write a lot more exception handlers, so it's a trade off... But it's at least another angle to consider.

    0 讨论(0)
  • 2021-01-30 06:54

    These links may help

    • Meanwhile... on the command side of my architecture
    • Returning data from command handlers
    • Meanwhile... on the query side of my architecture
    • and also this...
    0 讨论(0)
  • 2021-01-30 06:57

    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...

    0 讨论(0)
  • 2021-01-30 06:59

    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.

    0 讨论(0)
  • 2021-01-30 07:02

    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.

    0 讨论(0)
提交回复
热议问题