イベントハンドラーでこれを解決する1つの方法は、sagaを使用することです。ワークフローは次のようになります。
CancelOrder
コマンドを受信すると、aOrderCancellationSaga
が開始され、注文がCancelling
状態になります。
- ペイメントゲートウェイからの返金が確認されると、サガが完了し、注文はキャンセルされた状態になり、続行されます。この時点で、同じトランザクション内で
OrderCancelled
イベントが発生します。
- 支払いゲートウェイとのやり取りが失敗したり拒否されたりした場合、注文を以前の状態に戻すか、ある種のエラー状態にすることができます。
このシナリオでは、監査はどの段階でも実行できます。また、許可と、最初に注文をキャンセルできるかどうかは、サガを開始する前に、またはサガを開始する最初のステップとして確認する必要があります。
C#とNServiceBusのサガを使用した大まかなサンプル:
class OrderOrderCancellationSaga : Saga<OrderCancellationSagaData>
,IAmStartedBy<CancelOrderCommand>,
,IHandle<PaymentGatewayInteractionFailedEvent>
{
public OrderService OrderService { get; set; }
public PaymentGateway PaymentGateway { get; set; }
// correlate saga messages by order ID
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<PaymentGatewayInteractionFailedEvent>(x => x.OrderId, x =>x.OrderId);
ConfigureMapping<RefundCompletedEvent>(x => x.OrderId, x => x.OrderId);
}
// start cancellation process
public void Handle(CancelOrderCommand message)
{
// check if cancellation is authorized and valid
// ....
// can save prior state here, if needed
this.Data.OrderId = message.OrderId;
this.Data.State = "Cancelling";
this.Bus.Send(new RefundOrderCommand(...));
}
public void Handle(RefundCompletedEvent message)
{
this.Data.State = "Cancelled";
this.OrderService.CompleteCancellation(...);
MarkAsComplete();
}
// this handler can be hosted on a different endpoint.
public void Handle(RefundOrderCommand message)
{
try
{
this.PaymentGateway.Refund(...
}
catch(Exception ex)
{
this.Bus.Reply(new PaymentGatewayInteractionFailedEventmessage(...));
}
}
// this handler can be used to revert whole operation.
public void Handle(PaymentGatewayInteractionFailedEvent message)
{
// or revert to prior state.
this.Data.Status = "Cancellation Failed";
// call any application services needed.
// finishes saga, deleting state
MarkAsComplete();
}
}