モノリスとマイクロサービスのデータ管理の違い
モノリシックアーキテクチャでは、すべてのサービスが単一のデータベースを共有します。JOINが自由に使え、トランザクションも一度のBEGIN/COMMITで完結するため、データの整合性を維持しやすいです。しかしマイクロサービスアーキテクチャでは、各サービスが独立したデータベースを持つことが推奨されるため、データ管理が根本的に異なります。
パターン1:Database per Service(サービスごとのデータベース)
各マイクロサービスは自分専用のデータベースを持ち、他のサービスは直接アクセスしません。データが必要な場合はAPIを通じてリクエストします。
-- ユーザーサービスのDB(PostgreSQL)
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- 注文サービスのDB(PostgreSQL、別インスタンス)
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL, -- ← usersテーブルへの外部キーだが制約は持たない
total DECIMAL(10,2) NOT NULL,
status VARCHAR(50) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT NOW()
-- FOREIGN KEY制約は設定しない(別DBのため)
);
-- user_idの検証はアプリ層で行う(APIコール)
-- SELECT * FROM users WHERE id = $1 ← ユーザーサービスへのAPIリクエスト
パターン2:Sagaパターン(分散トランザクション)
マイクロサービスでは通常のACIDトランザクションが使えないため、Sagaパターンで最終的な整合性を確保します。各ステップが成功すれば次のステップに進み、失敗したら補償トランザクションでロールバックします。
-- Sagaの実行状態を追跡するテーブル
CREATE TABLE saga_executions (
saga_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
saga_type VARCHAR(100) NOT NULL,
status VARCHAR(20) DEFAULT 'started', -- started/completed/failed
current_step VARCHAR(100),
payload JSONB,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE saga_steps (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
saga_id UUID REFERENCES saga_executions(saga_id),
step_name VARCHAR(100) NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
compensating_data JSONB, -- ロールバックに必要なデータ
executed_at TIMESTAMP,
compensated_at TIMESTAMP
);
-- 注文作成Sagaの例:
-- 1. 在庫確認・予約(inventory-service)
-- 2. 支払い処理(payment-service)
-- 3. 注文確定(order-service)
-- ※ ステップ2失敗時 → ステップ1の補償:在庫予約を解除
パターン3:CQRS(コマンドクエリ責任分離)
読み取り(クエリ)と書き込み(コマンド)で異なるデータモデルを使用するパターンです。書き込み側は正規化された設計でデータ整合性を保ち、読み取り側は非正規化したビューで高速なクエリを実現します。
-- コマンド側(書き込み用:正規化されたスキーマ)
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE order_items (
id UUID PRIMARY KEY,
order_id UUID REFERENCES orders(id),
product_id UUID NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2) NOT NULL
);
-- クエリ側(読み取り用:非正規化されたビュー/テーブル)
-- イベントドリブンで非同期に更新
CREATE TABLE order_summaries (
order_id UUID PRIMARY KEY,
user_name VARCHAR(100),
user_email VARCHAR(255),
item_count INT,
total_price DECIMAL(10,2),
status VARCHAR(50),
created_at TIMESTAMP
);
-- イベントハンドラーで更新
-- ORDER_CREATED → order_summariesにINSERT
-- ORDER_STATUS_CHANGED → order_summariesをUPDATE
パターン4:イベントソーシング
状態ではなく変更イベントを保存するパターンです。過去のすべての変更履歴が残り、任意の時点の状態を再構築できます。
CREATE TABLE domain_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
aggregate_id UUID NOT NULL,
event_type VARCHAR(100) NOT NULL,
event_data JSONB NOT NULL,
version INT NOT NULL,
occurred_at TIMESTAMP DEFAULT NOW(),
UNIQUE(aggregate_id, version)
);
-- 注文の状態は全イベントを順に適用して再構築
-- INSERT events: ORDER_CREATED, ITEM_ADDED, ITEM_ADDED, PAYMENT_COMPLETED, ORDER_SHIPPED
SELECT event_type, event_data, occurred_at
FROM domain_events
WHERE aggregate_id = 'order-123'
ORDER BY version ASC;
Erdmatesでマイクロサービスのデータ構造を設計
マイクロサービスアーキテクチャでは、各サービスのデータモデルが独立しているため、全体像を把握しにくくなります。ErdmatesでサービスごとのERDを作成し、サービス間のデータフロー(APIで連携するフィールドなど)を可視化することで、チーム全体のアーキテクチャ理解が深まります。
まとめ
マイクロサービスのデータ管理には「正解」がなく、ビジネス要件とシステムの複雑さのトレードオフを常に考慮する必要があります。Database per Serviceを基本とし、分散トランザクションが必要な場合はSaga、高速な読み取りが必要な場合はCQRS、完全な監査ログが必要な場合はイベントソーシングと、目的に応じてパターンを組み合わせましょう。