
【イベントドリブン・マイクロサービスアーキテクチャ構築】RabbitMQによりEC用三つの基礎サービスの実装 - Springboot3 第17回(終)
はじめに
こんにちは、今日はSpringbootを使ったマイクロサービス構築の最後の連載です。ECサイトで重要なサービスをいくつか挙げるなら、注文、在庫、顧客(メール)だと思います。今日は前回に引き続きRabbitMQを使って実際に3つの独立したサービスを実装してマイクロサービスを構築してみます。

Order ServiceがProducer、
Stock ServiceとEmail ServiceがConsumerです。
RabbitMQメッセージブローカーの中にはExchangeとそれぞれのQueueが存在します。このExchangeはルーティングキーを通じてメッセージをキューにバインドします。
実装過程
プロジェクト作成

依存関係としてRabbitMQ, Web, Lombokを追加します。
以後は、stock-service, email-serviceと同じように作成します。
3つのサービスをモジュール・インポート


プロジェクトの中に表示されます。


Order-Serviceの構成
RabbitMQConfigの作成

まず、RabbitMQConfigを下記のように作成します。
@Configuration
public class RabbitMQConfig {
@Value("${rabbitmq.queue.order.name}")
private String orderQueue;
@Value("${rabbitmq.exchange.name}")
private String exchange;
@Value("${rabbitmq.binding.routing.key}")
private String orderRoutingKey;
// spring bean for queue - order queue
@Bean
public Queue orderQueue(){
return new Queue(orderQueue);
}
// spring bean for exchange
@Bean
public TopicExchange exchange(){
return new TopicExchange(exchange);
}
// spring bean for binding between exchange and queue using routing key
@Bean
public Binding binding(){
return BindingBuilder
.bind(orderQueue())
.to(exchange())
.with(orderRoutingKey);
}
// spring bean for binding between exchange and queue using routing key
@Bean
public Binding emailBinding(){
return BindingBuilder
.bind(emailQueue())
.to(exchange())
.with(emailRoutingKey);
}
// message converter
@Bean
public MessageConverter converter(){
return new Jackson2JsonMessageConverter();
}
// configure RabbitTemplate
@Bean
public AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(converter());
return rabbitTemplate;
}
}
このRabbitMQConfigクラスは、Spring BootアプリケーションでRabbitMQとの連携を構成するためのクラスです。
プロパティの注入
orderQueue, exchange, orderRoutingKeyという3つのプロパティが@Valueアノテーションを使用して注入されています。これらの値は、application.propertiesファイルから取得されます。
Queueの定義
orderQueue()メソッドは、RabbitMQのキューを表すQueueオブジェクトを作成します。このキューはorderQueueプロパティの値を使用して名前が設定されます。
Exchangeの定義
exchange()メソッドは、RabbitMQのExchangeを表すTopicExchangeオブジェクトを作成します。Exchangeはメッセージの配信先を制御します。exchangeプロパティの値が使用されます。
Bindingの定義
binding()メソッドは、ExchangeとQueueの間のバインディングを定義します。このバインディングには、orderQueue、exchange、およびorderRoutingKeyプロパティの値が使用されます。
メッセージコンバータの定義
converter()メソッドは、メッセージの変換を担当する
Jackson2JsonMessageConverterを作成します。これにより、メッセージはJSON形式に変換されます。
RabbitTemplateの設定
amqpTemplate()メソッドは、RabbitMQとの通信を簡素化するために使用されるRabbitTemplateを構成します。これには、前述のメッセージコンバータが設定されます。

rabbitmq.queue.order.name=order
rabbitmq.exchange.name=order_exchange
rabbitmq.binding.routing.key=order_routing_key
rabbitmq.queue.email.name=email
rabbitmq.binding.email.routing.key=email_routing_key

DTOとPublisher作成

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
private String orderId;
private String name;
private int qty;
private double price;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderEvent {
private String status; // pending, progress, completed
private String message;
private Order order;
}
@NoArgsConstructorアノテーションは、引数なしのデフォルトコンストラクタを生成します。デフォルトコンストラクタです。
@AllArgsConstructorアノテーションは、全てのフィールドを引数に持つコンストラクタを生成します。全フィールドを持つコンストラクタです。、DTOオブジェクトを生成しながら一度にすべてのフィールドの値を初期化できます。これにより、コードを簡潔に保ち、フィールドが追加または削除される際にコンストラクタを手動で更新する手間を自動的に処理できます。
@Service
public class OrderProducer {
private Logger LOGGER = LoggerFactory.getLogger(OrderProducer.class);
@Value("${rabbitmq.exchange.name}")
private String exchange;
@Value("${rabbitmq.binding.routing.key}")
private String orderRoutingKey;
private RabbitTemplate rabbitTemplate;
public OrderProducer(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void sendMessage(OrderEvent orderEvent){
LOGGER.info(String.format("Order event sent to RabbitMQ => %s", orderEvent.toString()));
// send an order event to order queue
rabbitTemplate.convertAndSend(exchange, orderRoutingKey, orderEvent);
}
}
OrderProducerクラスはRabbitMQにメッセージを送信するためのクラスです。
プロパティの注入
@Valueアノテーションを使用して、RabbitMQの交換(exchange)とルーティングキー(routing key)に関連するプロパティが注入されます。これらの値はアプリケーションの設定から取得されます。
RabbitTemplateの注入
コンストラクタを介してRabbitTemplateが注入されます。RabbitTemplateはRabbitMQとの通信を抽象化し、メッセージの送信を簡素化するのに使用されます。
sendMessageメソッド
sendMessageメソッドは、与えられたOrderEventオブジェクトをRabbitMQに送信します。まず、LOGGERを使用してログにメッセージを記録します。次に、rabbitTemplate.convertAndSendメソッドを使用して、指定された交換、ルーティングキー、およびOrderEventオブジェクトをRabbitMQに送信します。






送ったリクエストが確認されます。
設定の名前を変更したい場合は、名前を変更してSpring Bootアプリケーションを停止し、Dockerコンテナを停止してDockerコンテナを再実行します。その後、Spring Boot アプリケーションを再起動します。
Stock-Serviceの構成
RabbitMQの構成

@Configuration
public class RabbitMQConfig {
// message converter
@Bean
public MessageConverter converter(){
return new Jackson2JsonMessageConverter();
}
// configure RabbitTemplate
@Bean
public AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(converter());
return rabbitTemplate;
}
}
Consumerの構成

@Service
public class OrderConsumer {
private Logger LOGGER = LoggerFactory.getLogger(OrderConsumer.class);
@RabbitListener(queues = "${rabbitmq.queue.order.name}")
public void consume(OrderEvent event){
LOGGER.info(String.format("Order event received => %s", event.toString()));
// save order event data in database
}
}
このOrderConsumerクラスは、RabbitMQから注文イベントを受信して処理するためのサービスクラスです。
プロパティの注入
@RabbitListenerアノテーションを使用して、RabbitMQのキューの名前に関連するプロパティが注入されます。これはアプリケーションの設定から取得されます。
consumeメソッド
@RabbitListenerアノテーションが付いたconsumeメソッドは、指定されたRabbitMQキューからメッセージを受信します。メソッド内で、LOGGER.infoを使用して受信した注文イベントに関する情報をログに記録します。

ポート番号、rabbitmq.queue.order.nameを設定する。
server.port=8081
rabbitmq.queue.order.name=order


Consumerのメッセージ、表示されます!
うまくできた~!
EmailServicの構成
OrderService修正1 :メールキューの設定とバインディング

server.port=8083
rabbitmq.queue.order.name=order
rabbitmq.exchange.name=order_exchange
rabbitmq.binding.routing.key=order_routing_key
rabbitmq.queue.email.name=email
rabbitmq.binding.email.routing.key=email_routing_key

emailのqueue, routing key, bindingの関連のコードを作成します。
@Configuration
public class RabbitMQConfig {
@Value("${rabbitmq.queue.order.name}")
private String orderQueue;
@Value("${rabbitmq.queue.email.name}")
private String emailQueue;
@Value("${rabbitmq.exchange.name}")
private String exchange;
@Value("${rabbitmq.binding.routing.key}")
private String orderRoutingKey;
@Value("${rabbitmq.binding.email.routing.key}")
private String emailRoutingKey;
// spring bean for queue - order queue
@Bean
public Queue orderQueue(){
return new Queue(orderQueue);
}
// spring bean for queue - order queue
@Bean
public Queue emailQueue(){
return new Queue(emailQueue);
}
// spring bean for exchange
@Bean
public TopicExchange exchange(){
return new TopicExchange(exchange);
}
// spring bean for binding between exchange and queue using routing key
@Bean
public Binding binding(){
return BindingBuilder
.bind(orderQueue())
.to(exchange())
.with(orderRoutingKey);
}
// spring bean for binding between exchange and queue using routing key
@Bean
public Binding emailBinding(){
return BindingBuilder
.bind(emailQueue())
.to(exchange())
.with(emailRoutingKey);
}
// message converter
@Bean
public MessageConverter converter(){
return new Jackson2JsonMessageConverter();
}
// configure RabbitTemplate
@Bean
public AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(converter());
return rabbitTemplate;
}
}
OrderServiceの修正2:メールキューにイベントを送信すること

email関連のrouting_key設定注入、
sendMessageメソッド内の2回のconvertAndSend呼び出しにより、メッセージは2つの異なるキュー(orderRoutingKeyおよびemailRoutingKeyに基づく)に送信されます。
@Service
public class OrderProducer {
private Logger LOGGER = LoggerFactory.getLogger(OrderProducer.class);
@Value("${rabbitmq.exchange.name}")
private String exchange;
@Value("${rabbitmq.binding.routing.key}")
private String orderRoutingKey;
@Value("${rabbitmq.binding.email.routing.key}")
private String emailRoutingKey;
private RabbitTemplate rabbitTemplate;
public OrderProducer(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void sendMessage(OrderEvent orderEvent){
LOGGER.info(String.format("Order event sent to RabbitMQ => %s", orderEvent.toString()));
// send an order event to order queue
rabbitTemplate.convertAndSend(exchange, orderRoutingKey, orderEvent);
// send an order event to email queue
rabbitTemplate.convertAndSend(exchange, emailRoutingKey, orderEvent);
}
}

stock-serviceとほぼ同じ。「棚から牡丹餅」
メール・サービス・プロジェクトは、注文イベントを消費することができます。そのために、2つのDTO(Order,OrderEvent)クラスが必要です。
このメールサービスは基本的に注文イベントをJSON形式で消費するので、メッセージコンバータが必要です。
3つのマイクロサービス実行

Postmanでメッセジー送受信テスト




今日の勉強もコミット・プシュしました!
最後に
今までRabbitMQを使って注文、在庫、メール3つの違うサービスを実装してみました。 ほぼ1ヶ月半を費やしてマイクロサービスの概念と実際の構築過程を勉強してみましたが、モノリシック方式に慣れていて他のことを考えられないのに、このように柔軟にサービスを構成できることに改めて驚きました。
実際に自分で作ってみると、なぜ最近大型サービス会社がマイクロサービスアーキテクチャを採用するのかが分かる気がします。 結局、RabbitMQは前回勉強したKafkaとメッセージブローカーという点では似ていますが、少し違う形をしています。 RabbitMQは「高可用性」と「耐障害性」が要求される環境に一般的に使われる伝統的なメッセージブローカーであり、ユーザビリティーが良い方です。一方、Kafkaは「拡張性」と「大容量処理」に特化しており、イベントストリーミング、メッセージ再生、分散処理などの機能をサポートしているので、ビッグデータアプリケーション、IoT、リアルタイム分析に活用されることもあります。
このような点を考慮して技術スタックを慎重に検討する必要があり、次回からはフルスタックWebエンジニアを目指す私として、本当にやってみたかったプロジェクトを進めようと思います。バックエンドをSpringbootで、フロントエンドをReactで構成してテスト主導開発方式(TDD)で小さなウェブアプリケーションを開発することです。 それでは、次回にお会いしましょう!
エンジニアファーストの会社 株式会社CRE-CO
ソンさん
【参考】
[Udemy] Building Microservices with Spring Boot & Spring Cloud