问题是;当需要命令的结果时,如何应用 CQS?
答案是:你不知道。如果你想运行一个命令并返回一个结果,你没有使用 CQS。
然而,黑白教条的纯洁性可能是宇宙的死亡。总是存在边缘情况和灰色区域。问题是您开始创建一种 CQS 形式的模式,但不再是纯 CQS。
Monad 是一种可能性。您可以返回 Monad,而不是您的 Command 返回 void。 “void” Monad 可能如下所示:
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; }
}
现在你可以有一个像这样的“命令”方法:
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); // <-- should never fail BTW
return Monad.Failure(ex);
}
return Monad.Success();
}
灰色区域的问题在于它很容易被滥用。将诸如新的 OrderID 之类的返回信息放入 Monad 将允许消费者说,“忘记等待事件,我们在这里得到了 ID!!!”此外,并非所有命令都需要 Monad。您确实应该检查应用程序的结构,以确保您确实达到了边缘情况。
使用 Monad,现在您的命令消耗可能如下所示:
//some function child in the Call Stack of "CallBackendToCreateOrder"...
var order = CreateNewOrder(buyer, item, transactionGuid);
if (!order.Success || order.IsExceptionState)
... Do Something?
在很远的代码库中。 . .
_eventHandler.on("orderCreated", transactionGuid, out order)
_storeService.PerformPurchase(order);
在很远的 GUI 中。 . .
var transactionID = Guid.NewGuid();
OnCompletedPurchase(transactionID, x => {...});
OnException(transactionID, x => {...});
CallBackendToCreateOrder(orderDetails, transactionID);
现在你已经拥有了你想要的所有功能和适当性,只需要一点点用于 Monad 的灰色区域,但要确保你不会意外地通过 Monad 暴露出糟糕的模式,所以你限制了你可以做的事情它。