イベントの扱い方
現代のシステムにおいて、DDD(Domain-Driven Design)やクリーンアーキテクチャなどのアーキテクチャのベストプラクティスに従う場合、イベントは状態の変化をモデル化し、コンポーネントやシステム間の非同期通信を行うための基本的なメカニズムです。 イベントは、アプリケーションのドメイン内で既に発生した出来事を表し、システムの他の部分や外部サービスに関心がある場合があります。
ドメインイベント
ドメインイベントは、アプリケーションのコンテキスト内で発生した重要な事実を表します。 これらはシステム内部で生成および消費され、状態の変更を疎結合に伝播させることができます。つまり、オブジェクト同士が直接知る必要はありません。
ドメインイベントはいつ使用するのか?
- 操作によってドメイン内に重要な変更が生じ、システムの他の部分に通知またはトリガーを行う必要がある場合。
- ドメインモデルの一貫性を保ち、異なるプロセスを直接的な依存関係なしに起動できるようにする場合。
Lino では、新しいドメインイベントの作成は簡単です。次のコマンドを実行できます:
lino event new
CLIウィザードは以下を要求します:
- サービス – イベントが作成されるサービス。
- モジュール – イベントが作成されるモジュール(モジュール型サービスのみ)。
- エンティティ – イベントが作成/関連付けされるエンティティ。
- イベントタイプ – ドメインイベントまたは統合イベント。
- イベント名 – ドメインで使用され、エンティティに関連付けられる名前。
例
User エンティティに関連付けられたドメインイベント UserCreated を作成すると、
システムは自動的に UserCreatedDomainEvent という名前のイベントを生成します。
この名前により、イベントを消費するシステムのどの部分でも、ユーザー作成アクションがすでに完了していることが明確になります。
Lino によって生成された構造:
MyApp/
└── src/
└── Services/
└── MyService/
└── Domain/
├── MyApp.MyService.Domain.csproj
└── Aggregates/
└── Users/
├── User.cs
├── Errors/
├── Events/
│ └── UserCreatedDomainEvent.cs
├── Repositories/
└── Resources/
ドメインイベントハンドラー
Domain Event Handler は、ドメインイベント に反応し、アプリケーションの内部状態に関連するアクションを実行するクラスであり、常に同じトランザクションコンテキスト内で動作します。
これらのハンドラーの主な目的は、システムを一貫性のある状態で疎結合に保ち、エンティティやアグリゲートのコアロジックを過負荷にせずに追加ルールを適用できるようにすることです。
例えば、User を作成した後に、統計を更新したり、内部ログを生成したり、他のアグリゲートに通知したりする必要がある場合があります。
これらのアクションはドメイン内で意味があり、同期的に実行でき、即時の整合性を保証します。
しかし、外部リソースに依存する操作(メール送信やサードパーティAPIの呼び出しなど)は、ドメインイベントから直接実行すべきではありません。これによりトランザクションが遅延や不安定なタスクに縛られてしまうためです。 この場合、ドメインは統合イベント(Outboxに記録)を生成し、後で非同期かつ耐障害性のある方法で処理されます。
主な特徴:
- イベントに反応するが、決して変更しない。
- ドメインの整合性に関連するインプロセス操作のみを実行する。
- すべてが同じトランザクション内で実行されることを保証する。
新しいドメインイベントハンドラーを作成するには、次のコマンドを実行します:
lino event-handler new
CLIウィザードは以下を要求します:
- サービス – イベントハンドラーを作成するサービス。
- モジュール – イベントハンドラーを作成するモジュール(モジュラーサービスのみ)。
- エンティティ – イベントハンドラーを作成するエンティティ。
- イベントタイプ – ドメインイベントまたは統合イベント。
- イベント – 消費されるイベント。
- イベントハンドラー名 – エンティティとドメインイベントに関連付けられる名前。
例
User エンティティと UserCreatedDomainEvent イベントに関連付けられたドメインイベントハンドラー UserCreated を作成すると、システムは自動的に UserCreatedDomainEventHandler という名前のイベントハンドラーを生成します。
Linoによって生成された構造:
MyApp/
└── src/
└── Services/
└── MyService/
└── Application/
├── MyApp.MyService.Application.csproj
└── UseCases/
└── Users/
├── Commands/
├── EventHandlers/
│ └── Domain/
│ └── UserCreatedDomainEventHandler.cs
├── Logging/
├── Queries/
└── Resources/
統合イベント
統合イベントは、重要な出来事が発生したことを通知するメッセージであり、外部システムや他のマイクロサービスと共有する必要があります。
ドメインイベントとは異なり、ここでの目的はシステム間の通信と状態の同期です。
統合イベントを作成するタイミング:
- あなたのシステムの変更を他のシステムに反映させる必要がある場合。
- あなたのマイクロサービスが変更を公開し、他のマイクロサービスがそれに反応する必要がある場合。
ドメインイベントと統合イベントの主な違い:
| 項目 | ドメインイベント | 統合イベント |
|---|---|---|
| 対象 | 内部 | 外部 |
| 結合度 | 低(内部) | 必要(システム間) |
| 処理時間 | 即時 | 非同期の場合あり、配信保証付き |
| 永続化の必要性 | 必須ではない | あり(信頼性と耐障害性のため) |
Lino では、新しい統合イベントの作成は簡単です。 次のコマンドを実行できます:
lino event new
CLIウィザードで以下が求められます:
- サービス – イベントを作成するサービス。
- モジュール – イベントを作成するモジュール(モジュラーサービスの場合のみ)。
- エンティティ – イベントを作成 / 関連付けるエンティティ。
- イベントタイプ – ドメインイベントまたは統合イベント。
- イベント名 – エンティティに関連付けられる統合イベントで使用する名前。
例
User エンティティに関連付けられた統合イベント UserCreated を作成すると、システムは自動的に UserCreatedIntegrationEvent という名前のイベントを作成します。
この名前により、イベントを消費するシステムのどの部分にとっても、ユーザー作成のアクションが既に完了していることが明確になります。
Lino によって生成された構造:
MyApp/
└── src/
└── Services/
└── MyService/
└── IntegrationEvents/
├── MyApp.MyService.IntegrationEvents.csproj
└── Users/
└── UserCreatedIntegrationEvent.cs
統合イベントハンドラー
Integration Event Handler は、通常は他のサービスやコンテキストによって公開された Integration Event を消費し、 自身のドメイン内で特定のアクションを実行するクラスです。
これらのハンドラーは、RabbitMQ、Kafka、Azure Service Bus などのメッセージングメカニズムを通じてイベントを受け取り、 通常は Outbox パターンと組み合わせて使用され、信頼性の高い配信と非同期処理を保証します。
例えば、Identity サービスが UserCreated イベントを公開した場合、別のコンテキストの対応するハンドラーは、
ウェルカムメールを送信したり、外部 API を呼び出したりして反応することができます。
これらの操作は遅延したり失敗する可能性がありますが、メイントランザクションの外で処理されるため、アプリケーションの内部整合性は損なわれません。
主な特徴:
- 他のコンテキストに関連するビジネス上の事実を表すイベントに反応します。
- 遅い可能性のある操作や外部操作(例:メール送信、API 呼び出し)を実行します。
- 非同期かつレジリエントに処理され、リトライやモニタリングが行われることが多いです。
- 外部の失敗が元のドメイントランザクションに影響を与えないことを保証します。
- バウンデッドコンテキストや分散システム間の統合を容易にします。
新しい統合イベントハンドラーを作成するには、次のコマンドを実行します:
lino event-handler
CLI ウィザードは以下を要求します:
- サービス – イベントハンドラーを作成するサービス。
- モジュール – イベントハンドラーを作成するモジュール(モジュール型サービスのみ)。
- エンティティ – イベントハンドラーを作成するエンティティ。
- イベントタイプ – ドメインイベントまたは統合イベント。
- サービス – 消費するイベントが存在するサービス。
- モジュール – 消費するイベントが存在するモジュール(モジュール型サービスのみ)。
- エンティティ – 消費するイベントが存在するエンティティ。
- イベント – 消費するイベント。
- イベントハンドラー名 – エンティティおよび統合イベントに関連付けられる名前。
例
User エンティティおよび UserCreatedIntegrationEvent イベントに関連付けられた統合イベントハンドラー
SendEmailOnUserCreated を作成すると、システムは自動的に SendEmailOnUserCreatedIntegrationEventHandler という名前のハンドラーを作成します。
Lino によって生成された構造:
MyApp/
└── src/
└── Services/
└── MyService/
└── Application/
├── MyApp.MyService.Application.csproj
└── UseCases/
└── Users/
├── Commands/
├── EventHandlers/
│ └── Integration/
│ └── SendEmailOnUserCreatedIntegrationEventHandler.cs
├── Logging/
├── Queries/
└── Resources/
