web-dev-qa-db-ja.com

イベントソーシングにおけるコマンド、ドメインイベント、および外部イベントについて混乱

私は少し混乱しています

  • イベントの調達におけるコマンドの役割
  • ドメインと外部イベントの区別

私の理解が正しいなら

  • コマンドは、ドメインの観点からアクターによって開始されたアクションを表します
  • ドメインイベントは、集約ルートによって消費および生成できるイベントです。
  • 外部イベントはDTOのよ​​うなものです-データコントラクトであり、ドメインイベントまたはコマンドに変換する必要があります

Product集約ルートがあります。製品には、複数のアクティブな特別オファーがある場合があります。 SpecialOffersを管理するために、製品は2つのドメインイベントを受け入れます。

  • SpecialOfferActivated
  • SpecialOfferDeactivated

そのため、パブリックインターフェイスは、2つのオーバーロードされたApplyメソッドです。

class Product{
    ...
    Apply(SpecialOfferActivated){...}
    Apply(SpecialOfferDeactivated){...}
}

1番目のケース:リクエストはAPIのフロントエンドから送信されます

したがって、Controllerは行の最初にあります。基本的には、発信者の意図をデータコントラクト(DTO)からドメイン言語(コマンド)に変換します。

class ProductController{
    Post(SpecialOfferDto dto){
        ActivateSpecialOfferCommand command = Map(dto)
        _commandBus.Send(command)
    }
}

コマンドが送信されました。今度はコマンドハンドラが必要です

class ActivateSpecialOfferCommandHandler{
    Handle(ActivateSpecialOfferCommand command){
        SpecialOfferActivated domainEvent = Map(command)
        _eventBus.Publish(domainEvent)
    }
}

イベントが公開されました。今度はイベントハンドラの時間です

class SpecialOfferActivatedDomainEventHandler{
    Handle(SpecialOfferActivated domainEvent){
        var product = GetFromDatabase()
        product.Apply(domainEvent)
        Save(product)
    }
}

できました。

2番目のケース:プロセスは、サービスバスに発行されたexternalイベントによって開始されます。

今回はNewPromotionExternalEventがデータコントラクト(ExternalEvent)であり、ドメイン言語(Command)に翻訳する必要があります。

class NewPromotionExternalEventHandler{
    Handle(NewPromotionExternalEvent extenalEvent){
        ActivateSpecialOfferCommand command = Map(extenalEvent)
        _commandBus.Send(command)
    }
}

そして、最初のケースからActivateSpecialOfferCommandHandlerにフォールバックします。つまり、基本的には最初のケースと同じです。

3番目のケース:ドメインイベントレイヤーをスキップ(1番目または2番目のケースのバリエーション)

したがって、APIまたは外部イベントのいずれかによってコマンドが生成されました。集約ルートに適用するために、ドメインイベントを作成するだけです。 notイベントをサービスバスに発行します。

class ActivateSpecialOfferCommandHandler{
    Handle(ActivateSpecialOfferCommand command){
        SpecialOfferActivated domainEvent = Map(command)

        var product = GetFromDatabase()
        product.Apply(domainEvent )
        Save(product)
    }
}

できました。

4番目のケース:コマンドレイヤーをスキップ(1番目のケースのバリエーション)

コマンドレイヤーは簡単にスキップできます

class ProductController{
    Post(SpecialOfferDto dto){
        SpecialOfferActivated domainEvent = Map(dto)
        _eventBus.Publish(domainEvent)
    }
}

SpecialOfferActivatedDomainEventHandlerにフォールバック

5番目のケース:集約ルートの作成。

したがって、APIまたは外部イベントによって、コマンドCreateNewProductCommandが生成されました。そして、別のハンドラが必要です:

CreateNewProductCommandHandler{
    Handle(CreateNewProductCommand command){
        var product = Map(command)
        SaveToDatabase(product)

        NewProductCreated domainEvent = Map(product)
        _eventBus.Publish(domainEvent) // in case somebody is interested
    }
}

この場合、ドメインイベントレイヤーを貼り付ける場所は本当にありません。

6番目のケース:製品によって生成されたドメインイベント(集計ルート)

class Product{
    Apply(SpecialOfferActivated domainEvent){
        var specialOffer = Map(domainEvent)
        _specialOffers.Add(specialOffer)
        if(...){
            // For simplicity sake, assume the aggregate root can access _eventBus
            _eventBus.Publish(new ProductReceivedTooManySpromotionsDomainEvent(this.Id))
        }
    }
}

ご質問

  1. イベントレイヤーはクールで、複数のインスタンスや他のマイクロサービスにジョブを分散させることができます。しかし、コマンド層のポイントは何ですか?私はすぐにドメインイベントを簡単に生成できました(コントローラーまたは外部のイベントハンドラーで-4番目のケース)。
  2. 3番目のケースは合法ですか(パブリッシュせずに、集計ルートに適用するためだけにドメインイベントを作成します)?
  3. コマンドレイヤーは、ドメインイベントレイヤーが適用されていないときに、製品の作成を別のマイクロサービスに委任できるという5番目のケースでのみ意味がありますか?
  4. 外部イベントとドメインイベントの境界はどこですか? 2番目のNewPromotionExternalEventは本当に外部イベントですか、それともドメインイベントですか?
  5. ドメインイベントを生成できるのは誰ですか?集計ルート?コマンドハンドラ?ドメインイベントハンドラ?外部イベントハンドラ?別のマイクロサービス?それらすべて?
  6. domainイベントを別のマイクロサービスにディスパッチできますか?externalイベントになりますか?
  7. 製品の作成および特別オファーのアクティブ化がリクエストの形式である場合の適切な処理方法はコントローラまたは外部イベントです。 =?
1
Andrzej Gis

私は少し混乱しています

それはあなたのせいではありません。文学はひどい。

人々が実際に話しているのは、情報が「ドメインモデル」の周りを流れる方法です。

  • コマンドは新しい情報をドメインモデルに運ぶ
  • 「ドメインイベント」はモデルの状態の変更を説明します
  • 「外部イベント」は、モデルから情報awayを運びます。

これらの最初と最後は本当にmessagingについてです。

しかし、「ドメインイベント」のスペルはごちゃごちゃのようなものです。これらは、どちらもフロアワックスであり、デザートのトッピングです。 「イベントソーシング」のコンテキスト内では、それらは本当にpersistenceについてです。ただし、「ドメインイベント」という語句は、同じモデル内のアグリゲート間で情報を共有するために使用されるメッセージを説明するためにも使用されます。

コマンドレイヤーのポイントは何ですか?

ほとんどの場合、コントローラの懸念からアプリケーションの懸念(メッセージが正しい集約に配信され、トランザクションを処理することを保証する)を切り離す方法を提供します。また、ドメインモデルが機能する前に、特定の横断的関心事(タイミングなど)を適用する簡単な方法も提供します。

公開せずに、集約ルートに適用するためだけにドメインイベントを作成する

はい、しかし私はそれをいくぶん異なる綴りで書くでしょう-私たちは通常、集約の外でcreateイベントを行いません。アグリゲートは、状態の変更方法を決定するビジネスロジックを所有します(データのダムバッグではありません)。しかし、それが起こったことを世界に通知することなく、集合体が状態を変更することは完全に正常です。

外部イベントとドメインイベントの境界はどこですか?

通常、「外部イベント」はドメインイベントよりもはるかに薄いです。通常、サービスの個人情報をどこにでも共有したくないので、通常、内部実装の詳細のすべてを公開することはありません。また、外部イベントはサービス間の契約の一部です-その契約を変更するための戦略は、より多くの利害関係者を満足させる必要があります。ただし、単一のサービス内で維持されるデータは、変更にかかるコストが低くなります。

ドメインイベントを作成できるのは誰ですか?

集約ルート。理論的には、他のエンティティも同じ集合体に含まれています。

リクエストがコントローラーまたは外部イベントから来たときに、製品の作成と特別オファーのアクティブ化を処理する適切な方法は何ですか?

入ってくる情報->ドメインモデルの適切な部分をメモリにロードし、それに情報を渡して、変更を計算できるようにします。

集約ルートが唯一のドメインイベントを生成する場合、問題が発生します。集合体と対話する(そしてそれが独自のドメインイベントを生成する)唯一の方法は、ドメインイベントをそれに適用することです。

通常、集約ルートと対話する方法は、コマンドを介して集約ルートに情報を送信することです。

たとえば、 DDDサンプルアプリ を見てください。特に、ドメインモデル( ここ および ここ )に対して アプリケーションレイヤー がビジネスロジックをどのように実行していないかに注意してください。

また、ドメインイベントは、同じドメインモデル内の他のアグリゲートと通信するのに役立つと述べました。ドメインモデルは単一のマイクロサービスに限定されていないと思います。

歴史的に、ドメインイベントは、同じトランザクションに参加している他のアグリゲートと通信するために使用されていました。つまり、同じトランザクションに参加している場合、それらは同じマイクロサービスの一部です。

トランザクション境界を越えて情報を共有する場合、トランザクション内で情報を共有する場合とは設計上の考慮事項が異なります。

私は最近、人々がマイクロサービスの境界を境界のあるコンテキストに揃えていることを聞く可能性が高くなると思います。つまり、モデルは共有されません(理にかなっているはずです-マイクロサービスのポイントの一部は、独立して進化することができます)。

1
VoiceOfUnreason