Spring Boot と RabbitMQ の連携:Fanout Exchange による全宛先同時配信のパターン

Fanout 交換機の基本原理

RabbitMQ の Fanout 型交換機は、ルーティングキーの評価処理をスキップし、自身が持つバインドリストに登録されている全てのメッセージキューへ同一ペイロードを複製して転送する設計パターンです。この特性を活用することで、イベント発行・通知展開やログ分散処理など、ワンソースからマルチターゲットへの同期通信が効率的に実装できます。

1. 依存関係の取得

Spring Boot の自動構成機能を使用するため、プロジェクトビルドファイルに AMQP Starter を追加します。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2. インフラストラクチャーのプログラム的定義

GUIツールを用いた手作業登録を排除し、起動タイミングで `AmqpAdmin` インタフェースを通じて交換機・キュー・バインディング関係を自動生成します。以下の構成クラスは `ApplicationReadyEvent` を契機に基盤構築を行います。

@Component
public class BrochureInfrastructure {

    private final AmqpAdmin adminClient;

    public BrochureInfrastructure(AmqpAdmin adminClient) {
        this.adminClient = adminClient;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void buildMessagingTopology() {
        String targetExchange = "broadcast_domain_exchange";

        adminClient.declareExchange(new FanoutExchange(targetExchange, true, false));

        Queue emailTarget = new Queue("dest_queue_email", true);
        Queue smsTarget = new Queue("dest_queue_sms", true);
        
        adminClient.declareQueue(emailTarget);
        adminClient.declareQueue(smsTarget);

        adminClient.declareBinding(BindingBuilder.bind(emailTarget)
                .to(new FanoutExchange(targetExchange)));
        adminClient.declareBinding(BindingBuilder.bind(smsTarget)
                .to(new FanoutExchange(targetExchange)));

        System.out.println("メッセージ配信用インフラの初期化が完了しました。");
    }
}

3. 接続パラメータの設定

YAML形式の設定ファイルに Broker エンドポイント情報を記載します。仮想パスは規定値との兼ね合いで省略可能ですが、明示的に定義することで環境分離を明確にします。

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /

4. JSONシリアライズルールの適用

デフォルトの状態ではオブジェクト転送時に制限がかかるため、標準的な JSON フォーマットへの変換ルールを Bean レジストリに登録します。

@Configuration
public class DataTransformationRules {

    @Bean
    public MessageConverter jsonSerializer() {
        return new Jackson2JsonMessageConverter();
    }
}

送信対象となる DTO クラス定義:

@Data
@AllArgsConstructor
public class DistributionRecord {
    private Long traceId;
    private String eventType;
    private int priorityIndex;
}

5. メッセージ送信プロセスの実装

`RabbitTemplate` を介して交換機へデータを流し込みます。Fanout ルーティング則により、第3引数のルーティングキー部分には任意の値を渡すか空白文字列を指定します。

@Service
public class EventEmitterService {

    private final RabbitTemplate dispatcher;

    public EventEmitterService(RabbitTemplate dispatcher) {
        this.dispatcher = dispatcher;
    }

    public void emitBatchRecords(int loopCount) {
        for (int i = 0; i < loopCount; i++) {
            DistributionRecord payload = new DistributionRecord(
                (long)(i + 8000L),
                "SYSTEM_HEALTH_CHECK",
                3
            );
            
            dispatcher.convertAndSend("broadcast_domain_exchange", "", payload);
        }
        System.out.println(loopCount + "件のレコードが交換機に投入されました。");
    }
}

6. コンシューマー側リスナーの実装

`@RabbitListener` 属性により特定キューのポーリング制御を行います。前述のコンバーターが有効なため、メソッドの引数に直結した DTO 型を宣言すると自動的に JSON から Java オブジェクトへ復元されます。

@Component
public class BatchIngestionHandler {

    private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(BatchIngestionHandler.class.getName());

    private long emailProcessingCount = 0;
    private long smsProcessingCount = 0;

    @RabbitListener(queues = "dest_queue_email")
    public void processForEmail(DistributionRecord record) {
        emailProcessingCount++;
        log.info("[EMAIL CH] 処理番号:{}. ID={} | イベント種別={}",
                 emailProcessingCount, record.getTraceId(), record.getEventType());
    }

    @RabbitListener(queues = "dest_queue_sms")
    public void processForSms(DistributionRecord record) {
        smsProcessingCount++;
        log.info("[SMS CH]   処理番号:{}. ID={} | イベント種別={}",
                 smsProcessingCount, record.getTraceId(), record.getEventType());
    }
}

上記フローに従ってアプリケーションを実行すると、交換機を経由した発行パケットが自動的に重複展開され、定義された各キューで個別のスレッドが割り当てられて処理を進めます。 Broker 管理画面ではメッセージの滞留数や受領実績をリアルタイムで確認できるほか、エラー発生時の DLQ(死信キュー)連携も同様の宣言的スタイルで拡張可能です。

タグ: spring-boot RabbitMQ fanout-exchange spring-amqp message-broker

8月28日 12:03 投稿