Question: How to apply CQS when you need the result of a command?
Answer: You do not. If you want to run the command and return the result, you are not using CQS.
However, black and white dogmatic purity could be the death of the universe. There are always edge cases and gray areas. The problem is that you are starting to create templates that are a form of CQS, but not more pure CQS.
Monad is an opportunity. Instead of your team returning the void, you could return the Monad. The "Void" Monad may look like this:
public class Monad { private Monad() { Success = true; } private Monad(Exception ex) { IsExceptionState = true; Exception = ex; } public static Monad Success() => new Monad(); public static Monad Failure(Exception ex) => new Monad(ex); public bool Success { get; private set; } public bool IsExceptionState { get; private set; } public Exception Exception { get; private set; } }
You may now have this “Command” method:
public Monad CreateNewOrder(CustomerEntity buyer, ProductEntity item, Guid transactionGuid) { if (buyer == null || string.IsNullOrWhiteSpace(buyer.FirstName)) return Monad.Failure(new ValidationException("First Name Required")); try { var orderWithNewID = ... Do Heavy Lifting Here ...; _eventHandler.Raise("orderCreated", orderWithNewID, transactionGuid); } catch (Exception ex) { _eventHandler.RaiseException("orderFailure", ex, transactionGuid);
The problem with the gray area is that it is easily abused. Entering return information, such as the new OrderID in Monad, will allow consumers to say: "Forget waiting for the event, we have an identifier here!" In addition, not all teams require a Monad. You really have to check the structure of your application to make sure that you have really reached the edge.
In Monad, your command consumption may now look like this:
//some function child in the Call Stack of "CallBackendToCreateOrder"... var order = CreateNewOrder(buyer, item, transactionGuid); if (!order.Success || order.IsExceptionState) ... Do Something?
The code space is far.,.
_eventHandler.on("orderCreated", transactionGuid, out order) _storeService.PerformPurchase(order);
The GUI is far away.,.
var transactionID = Guid.NewGuid(); OnCompletedPurchase(transactionID, x => {...}); OnException(transactionID, x => {...}); CallBackendToCreateOrder(orderDetails, transactionID);
Now you have all the functionality and convenience you want, with only a few gray areas for Monad, but MAKE SURE that you do not accidentally expose a bad template through Monad, so you limit what you can do with it.