CQRSではバグを修正するのは簡単で、イベントを再デプロイして再生するだけだと言われています。
ただし、イベントを再生するだけでアイテムが2回出荷される場合、イベントの1つが原因で、制御下にない外部システムが顧客に「アイテムを出荷」する場合はどうなりますか。
それをどのように解決しますか?
読み取りモデルの状態を変更するイベントと、(潜在的に)外部システムの状態を変更するイベントを明確に分離する必要があります。両方の状態を一緒に変更する「混合イベント」がないことを確認してください。これにより、外部システムのイベントが再度発生しない特定の「再生モード」でイベントを再生できます。このモードでは、外部システムによって最初に開始されたイベントも「シミュレート」します(今では、代わりに再生キューからイベントを取得します)。
忘れないでください。再デプロイのステップは、実際には、読み取りモデルの状態を以前の時点にリセットすることを意味します。これは、おそらく外部システムの状態に対して実行できる(または実行する必要がある)ことではありません。
Martin Fowlerの イベントソーシング 記事から:
イベントソーシングの基本的な考え方は、アプリケーションの状態に対するすべての変更がイベントオブジェクトに確実にキャプチャされ、これらのイベントオブジェクト自体が、アプリケーションの状態自体と同じ存続期間に適用された順序で保存されるというものです。
したがって、システムの状態を特定の瞬間に復元する必要がある場合は、イベントハンドラーではなく、保存された状態をその瞬間まで再生します。
とはいえ、状態データのみを操作する場合は、外部システムに影響はありません。イベントストアにトリガーまたはウォッチャーがない場合は、復元中にこれらを無効にする必要があります。外部システムを制御できないと言っているので、外部システムにどのような副作用があるかわからないため、公開されたAPIを使用して状態を復元しようとすることはできません。復元によってシステムが中間状態になる場合(たとえば、外部システムでの操作の失敗が原因)、これはイベント再生の責任の範囲内に含まれるべきではありません。
ただし、イベントを再生するだけでアイテムが2回出荷される場合、イベントの1つが原因で、制御下にない外部システムが顧客に「アイテムを出荷」する場合はどうなりますか。
特定の例を選択するために、副作用に対する「少なくとも1回」のアプローチがどのように機能するかを考えてみましょう。
State currentState = State.InitialState
for(Event e : events) {
currentState = currentState.apply(e)
}
for(SideEffect s : currentState.querySideEffects()) {
performSideEffect(s)
したがって、ドメインモデルは実行する必要があることを追跡します。しかし、実際の処理はアプリケーションに任せます
コマンドを実行するコンテキストでは、基本的な考え方は同じに見えます。実際の副作用は発生しますoutsideモデルを更新するトランザクションの=。
したがって、モデルの単体テストは次のようになります。
{
// Given
State currentState = State.InitialState
// When
Events events = List.of(OrderPlaced)
// Then
List.of(SendEmail) === currentState.applyAll(events).querySideEffects()
}
{
// Given
State currentState = State.InitialState
// When
Events events = List.of(OrderPlaced, EmailSent)
// Then
List.EMPTY === currentState.applyAll(events).querySideEffects()
}
ここでの主なポイントは