問題は; コマンドの結果が必要な場合、どのようにCQSを適用しますか?
答えは:あなたはしません。コマンドを実行して結果を取得する場合は、CQSを使用していません。
しかし、黒と白の独断的な純粋さは宇宙の死である可能性があります。常にエッジケースと灰色の領域があります。問題は、CQSの形式であるが、純粋なCQSではなくなったパターンの作成を開始することです。
モナドは可能性です。コマンドがvoidを返す代わりに、Monadを返すことができます。「void」モナドは次のようになります。
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; }
}
これで、次のような「Command」メソッドを使用できます。
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などの返品情報をモナドに入れると、消費者は「イベントを待つのを忘れて、ここにIDがあります!!!」と言うことができます。また、すべてのコマンドがモナドを必要とするわけではありません。アプリケーションの構造を実際にチェックして、本当にエッジケースに到達したことを確認する必要があります。
モナドを使用すると、コマンドの消費量は次のようになります。
//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);
これで、モナドのわずかな灰色の領域で必要なすべての機能と適切性が得られましたが、モナドを通して誤って悪いパターンを公開していないことを確認してください。そのため、モナドでできることを制限します。