近年、マイクロサービスアーキテクチャや分散システムが普及するにつれ、データの整合性を保ちつつ、パフォーマンスを向上させる ことが重要になっています。
その解決策の一つとして、CQRS(Command Query Responsibility Segregation)とイベントソーシング が注目されています。
本記事では、CQRSとイベントソーシングの基本概念、オブジェクト指向設計との関係、Javaを用いた実装例を紹介します。
1. CQRS(コマンドクエリ責務分離)とは?
1-1. CQRSの基本概念
CQRS(Command Query Responsibility Segregation:コマンドクエリ責務分離) は、データの書き込み(Command)と読み込み(Query)の処理を分離する設計パターン です。
従来のアーキテクチャでは、データベースの読み書きが同じモデルで行われるため、パフォーマンスやスケーラビリティに課題が生じることがありました。
CQRSでは、以下のように書き込み用(Command) と 読み込み用(Query) のモデルを分離します。
項目 | 従来のアプローチ | CQRS |
---|---|---|
データ操作 | 読み書きが同じデータモデル | 読み書きを異なるモデルで処理 |
パフォーマンス | 読み取りと書き込みが競合しやすい | 読み取り専用のデータ構造を利用可能 |
スケーラビリティ | 同じDBを使うため負荷が集中 | 読み取りと書き込みを別々にスケール可能 |
データの一貫性 | 即時整合性が必要 | 最終的な整合性を考慮 |
2. CQRSの実装
2-1. CQRSのアーキテクチャ
CQRSを適用したアーキテクチャは、次のような構成になります。
+-------------------------------+ | クライアント(API, UI) | +-------------------------------+ | | ▼ ▼ +-------------------------------+ | コマンドハンドラー(Command) | | (注文作成、更新) | +-------------------------------+ | ▼ +-------------------------------+ | 書き込みデータストア(DB) | +-------------------------------+ | ▼ +-------------------------------+ | イベントハンドラー(Event) | +-------------------------------+ | ▼ +-------------------------------+ | 読み込みデータストア(Read DB) | +-------------------------------+ | ▼ +-------------------------------+ | クエリハンドラー(Query) | | (注文一覧取得) | +-------------------------------+
2-2. コマンド(Command)の実装
コマンドは、データの変更(作成・更新・削除) を担当します。
注文作成コマンド
public class CreateOrderCommand { private String orderId; private String customerName; private double amount; public CreateOrderCommand(String orderId, String customerName, double amount) { this.orderId = orderId; this.customerName = customerName; this.amount = amount; } public String getOrderId() { return orderId; } public String getCustomerName() { return customerName; } public double getAmount() { return amount; } }
コマンドハンドラー
import org.springframework.stereotype.Service; @Service public class OrderCommandHandler { private final OrderRepository orderRepository; public OrderCommandHandler(OrderRepository orderRepository) { this.orderRepository = orderRepository; } public void handle(CreateOrderCommand command) { Order order = new Order(command.getOrderId(), command.getCustomerName(), command.getAmount()); orderRepository.save(order); } }
2-3. クエリ(Query)の実装
クエリは、データの取得(検索) を担当します。
クエリモデル
public class OrderQueryModel { private String orderId; private String customerName; private double amount; public OrderQueryModel(String orderId, String customerName, double amount) { this.orderId = orderId; this.customerName = customerName; this.amount = amount; } public String getOrderId() { return orderId; } public String getCustomerName() { return customerName; } public double getAmount() { return amount; } }
クエリハンドラー
import org.springframework.stereotype.Service; import java.util.List; @Service public class OrderQueryHandler { private final OrderReadRepository orderReadRepository; public OrderQueryHandler(OrderReadRepository orderReadRepository) { this.orderReadRepository = orderReadRepository; } public List<OrderQueryModel> handle() { return orderReadRepository.findAll(); } }
3. イベントソーシングの活用
3-1. イベントソーシングとは?
イベントソーシング(Event Sourcing) は、データの状態をイベントの履歴 として記録し、イベントの再生によって現在の状態を構築する手法です。
3-2. イベントの定義
public class OrderCreatedEvent { private String orderId; private String customerName; private double amount; public OrderCreatedEvent(String orderId, String customerName, double amount) { this.orderId = orderId; this.customerName = customerName; this.amount = amount; } public String getOrderId() { return orderId; } public String getCustomerName() { return customerName; } public double getAmount() { return amount; } }
3-3. イベントを処理するイベントハンドラー
import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Service; @Service public class OrderEventHandler { private final OrderReadRepository orderReadRepository; public OrderEventHandler(OrderReadRepository orderReadRepository) { this.orderReadRepository = orderReadRepository; } @KafkaListener(topics = "order-events", groupId = "order-group") public void handleOrderCreated(OrderCreatedEvent event) { OrderQueryModel queryModel = new OrderQueryModel(event.getOrderId(), event.getCustomerName(), event.getAmount()); orderReadRepository.save(queryModel); } }
ポイント:
- OrderService
はイベントを発行し、OrderEventHandler
はイベントを処理して 読み込み用データベース を更新。
- 最終的な整合性(Eventual Consistency) を確保しつつ、高速なデータ取得が可能になる。
4. まとめ
CQRSとイベントソーシングを活用することで、パフォーマンスと整合性のバランスを最適化 できます。
特に、読み取りと書き込みを分離することでスケーラビリティを向上 させ、イベントソーシングによりデータの履歴を管理 できます。
次回の記事では、以下のトピックを取り上げる予定です:
質問やリクエストがあれば、ぜひコメントでお知らせください! 🚀