mcpField
S
spring-boot-event-driven-patterns

spring-boot-event-driven-patterns

github.com/giuseppe-trisciuoglio開発3653.8k 回インストール7 週間前に更新
リポジトリ

spring-boot-event-driven-patterns について

Spring Boot 3.xでイベント駆動アーキテクチャを実装する開発者向けの実践ガイドです。ドメインイベントやトランザクションリスナー、Kafkaに加え、Transactional outboxや再試行などの信頼性パターンを扱います。

主な機能

  1. 01ドメインイベントの設計と公開
  2. 02コミット後のイベント処理
  3. 03KafkaとSpring Cloud Stream
  4. 04Transactional outboxパターン
  5. 05再試行、冪等性、デッドレターキュー

活用シーン

  1. 01同期呼び出しからイベント通信への移行
  2. 02集約ルートからのドメインイベント公開
  3. 03トランザクションコミット後のイベント処理
  4. 04信頼性の高いKafkaコンシューマーの構築

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill spring-boot-event-driven-patterns

インストール後、Claude Code で/spring-boot-event-driven-patternsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
aggregate-root-patterns.md
configuration.md
dependency-setup.md
domain-events-design.md
event-driven-patterns-reference.md
event-handling.md
event-publishing.md
examples.md
outbox-pattern.md
testing-strategies.md

詳細

Spring Boot のイベント駆動パターン

概要

ドメインイベント、ApplicationEventPublisher、@TransactionalEventListener、および Kafka と Spring Cloud Stream による分散メッセージングを使用して、Spring Boot 3.x でイベント駆動アーキテクチャ(EDA)のパターンを実装します。

使用する場面

  • Kafka メッセージングを使用するイベント駆動マイクロサービスを実装する場合
  • DDD アーキテクチャで集約ルートからドメインイベントを発行する場合
  • データベースのコミット後に実行されるトランザクションイベントリスナーを設定する場合
  • Spring Kafka を使用してプロデューサーとコンシューマーによる非同期メッセージングを追加する場合
  • トランザクショナルアウトボックスパターンを使用してイベントの確実な配信を保証する場合
  • サービス間の同期呼び出しをイベントベースの通信に置き換える場合

クイックリファレンス

概念説明
ドメインイベントeventId、occurredAt、correlationId を持つ DomainEvent 基底クラスを拡張した不変イベント
イベントの発行ローカルでは ApplicationEventPublisher.publishEvent()、分散環境では KafkaTemplate
イベントのリスニング確実に処理するには @TransactionalEventListener(phase = AFTER_COMMIT) を使用
Kafka分散イベントのコンシュームには @KafkaListener(topics = "...") を使用
Spring Cloud StreamConsumer Bean を使用する関数型プログラミングモデル
アウトボックスパターンビジネスデータとイベントをアトミックに保存し、スケジュールされたパブリッシャーで発行

例

モノリシックからイベント駆動型へのリファクタリング

変更前(アンチパターン):

@Transactional
public Order processOrder(OrderRequest request) {
    Order order = orderRepository.save(request);
    inventoryService.reserve(order.getItems()); // Blocking
    paymentService.charge(order.getPayment()); // Blocking
    emailService.sendConfirmation(order); // Blocking
    return order;
}

変更後(イベント駆動型):

@Transactional
public Order processOrder(OrderRequest request) {
    Order order = Order.create(request);
    orderRepository.save(order);

    // Publish event after transaction commits
    eventPublisher.publishEvent(new OrderCreatedEvent(order.getId(), order.getItems()));

    return order;
}

@Component
public class OrderEventHandler {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleOrderCreated(OrderCreatedEvent event) {
        // Execute asynchronously after the order is saved
        inventoryService.reserve(event.getItems());
        paymentService.charge(event.getPayment());
    }
}

完全な動作例については、examples.md を参照してください。

手順

1. ドメインイベントを設計する

DomainEvent 基底クラスを拡張する不変イベントクラスを作成します。

public abstract class DomainEvent {
    private final UUID eventId;
    private final LocalDateTime occurredAt;
    private final UUID correlationId;
}

public class ProductCreatedEvent extends DomainEvent {
    private final ProductId productId;
    private final String name;
    private final BigDecimal price;
}

パターンについては、domain-events-design.md を参照してください。

2. 集約からイベントを発行する

集約ルートにドメインイベントを追加し、ApplicationEventPublisher を使用して発行します。

@Service
@Transactional
public class ProductService {
    public Product createProduct(CreateProductRequest request) {
        Product product = Product.create(request.getName(), request.getPrice(), request.getStock());
        repository.save(product);

        product.getDomainEvents().forEach(eventPublisher::publishEvent);
        product.clearDomainEvents();

        return product;
    }
}

DDD パターンについては、aggregate-root-patterns.md を参照してください。

3. トランザクション内でイベントを処理する

確実にイベントを処理するには、@TransactionalEventListener を使用します。

@Component
public class ProductEventHandler {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void onProductCreated(ProductCreatedEvent event) {
        notificationService.sendProductCreatedNotification(event.getName());
    }
}

検証: ハンドラーの実行前にデータベースの状態がコミット済みであることを確認し、イベントハンドラーがトランザクションのコミット後にのみ実行されることを確認します。

処理パターンについては、event-handling.md を参照してください。

4. Kafka インフラストラクチャを設定する

発行には KafkaTemplate を、コンシュームには @KafkaListener を設定します。

spring:
  kafka:
    bootstrap-servers: localhost:9092
    producer:
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer

検証: KafkaTemplate を介してテストイベントを送信し、本番向けのパターンに進む前に、コンシューマーのログに表示されることを確認します。

dependency-setup.md と configuration.md を参照してください。

5. アウトボックスパターンを実装する

イベントをアトミックに保存するための OutboxEvent エンティティを作成します。

@Entity
public class OutboxEvent {
    private UUID id;
    private String aggregateId;
    private String eventType;
    private String payload;
    private LocalDateTime publishedAt;
}

検証: スケジュール実行後に publishedAt タイムスタンプが設定されていることを確認し、スケジュールプロセッサーが保留中のイベントを取得することを確かめます。

スケジュールプロセッサーが保留中のイベントを発行します。outbox-pattern.md を参照してください。

6. 障害シナリオに対処する

再試行ロジック、デッドレターキュー、冪等性のあるハンドラーを実装します。

@RetryableTopic(attempts = "3")
@KafkaListener(topics = "product-events")
public void handleProductEvent(ProductCreatedEventDto event) {
    orderService.onProductCreated(event);
}

検証: 可観測性に進む前に、再試行を使い切ったメッセージがデッドレタートピックに到達することを確認します。

7. 可観測性を追加する

分散トレーシングのために Spring Cloud Sleuth を有効にし、メトリクスを監視します。

ベストプラクティス

  • 過去形で命名する:ProductCreated(CreateProductではなく)
  • イベントは不変に保つ:すべてのフィールドをfinalにします
  • 相関IDを含める:サービス間でイベントを追跡するために使用します
  • AFTER_COMMITフェーズを使用する:データベーストランザクションの成功後にイベントが発行されるようにします
  • 冪等なハンドラーを実装する:重複イベントを適切に処理します
  • 再試行の仕組みを追加する:イベント処理に失敗した場合は、指数バックオフで再試行します
  • デッドレターキューを実装する:再試行後も処理に失敗するイベントに対応します
  • すべての失敗をログに記録する:デバッグに十分なコンテキストを含めます
  • ハンドラーを順序に依存しないようにする:分散システムではイベントの順序が保証されません
  • イベント処理をバッチ化する:大量のイベントを処理する場合に使用します
  • イベントのレイテンシーを監視する:処理の遅延を検知するアラートを設定します

参考資料

制約と警告

  • @TransactionalEventListenerで発行されるイベントは、トランザクションのコミット後にのみ発火します
  • イベントで大きなオブジェクトを発行しないでください(メモリへの負荷やシリアライズの問題につながります)
  • 非同期イベントハンドラーの使用には注意してください(別スレッドや並行処理の問題が発生する可能性があります)
  • Kafkaのコンシューマーは重複メッセージを処理できる必要があります(冪等な処理を実装してください)
  • 分散システムではイベントの順序が保証されません(順序に依存しないハンドラーを設計してください)
  • メイントランザクションスレッドのイベントリスナーで、ブロッキング処理を実行しないでください
  • イベント処理のバックログを監視してください(システムの処理能力に問題がある兆候です)

関連スキル

  • spring-boot-security-jwt — 安全なイベント発行のためのJWT認証
  • spring-boot-test-patterns — イベント駆動型アプリケーションのテスト
  • aws-sdk-java-v2-lambda — AWS Lambdaによるイベント駆動処理
  • langchain4j-tool-function-calling-patterns — AI駆動のイベント処理

コメント