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