マイクロサービスアーキテクチャ完全ガイド

マイクロサービスアーキテクチャ分散システムDockerKubernetesAPI GatewayサービスメッシュDevOpsスケーラビリティクラウドネイティブ

マイクロサービスアーキテクチャ完全ガイド

概要

マイクロサービスアーキテクチャは、アプリケーションを独立してデプロイ可能で疎結合なサービスの集合として構造化するアーキテクチャパターンです。この実践ガイドでは、マイクロサービスの設計原則、サービス分割戦略、データ管理、DevOps実装まで包括的に解説します。

現代のクラウドネイティブアプリケーション開発において、マイクロサービスアーキテクチャは以下の理由で注目されています:

  • 独立したデプロイメント: 各サービスを個別にリリース・更新可能
  • 技術スタックの多様性: サービスごとに最適な技術選択が可能
  • チーム自律性: 小規模チームが独立して開発・運用可能
  • 障害の局所化: 一つのサービス障害が全体に波及しない
  • 水平スケーラビリティ: 需要に応じて特定サービスのみスケール可能

詳細

マイクロサービスの核心原則

1. ビジネス機能による分割

マイクロサービスは技術的な層ではなく、ビジネス機能に基づいて分割すべきです。これはドメイン駆動設計(DDD)の境界コンテキストと密接に関連しています。

良い分割例

  • ユーザー管理サービス
  • 注文処理サービス
  • 支払いサービス
  • 在庫管理サービス

悪い分割例

  • データベース層サービス
  • UI層サービス
  • ビジネスロジック層サービス

2. データの独立性

各マイクロサービスは独自のデータベースを持ち、他のサービスのデータベースに直接アクセスしてはいけません。これにより疎結合が保たれ、サービスの独立性が確保されます。

3. 分散メッセージバスの活用

サービス間の通信は、直接的な同期呼び出しを避け、イベントドリブンなアーキテクチャを採用することが推奨されます。

主要なデザインパターン

API Gateway パターン

API Gatewayは、すべてのクライアントリクエストの単一エントリーポイントとして機能し、適切なマイクロサービスにルーティングします。

機能

  • 認証・認可
  • リクエストルーティング
  • レート制限
  • ロードバランシング
  • ログ収集
  • レスポンス変換

Database per Service パターン

各マイクロサービスは専用のデータベースを持ち、データの独立性を保ちます。

利点

  • サービス間の疎結合
  • 技術スタックの柔軟性
  • 独立したスケーリング
  • 障害の局所化

Saga パターン

分散環境での複数サービス間のトランザクション整合性を保つためのパターンです。

実装方式

  • Choreography: イベントベースの分散実行
  • Orchestration: 中央制御による実行順序管理

Circuit Breaker パターン

サービス間の通信で障害が発生した際に、連鎖的な障害を防ぐためのパターンです。

状態遷移

  1. Closed: 正常状態、すべてのリクエストを通す
  2. Open: 障害状態、すべてのリクエストを遮断
  3. Half-Open: 回復確認状態、一部のリクエストで確認

Bulkhead パターン

システムの異なる部分を隔離し、一つのコンポーネントの障害が他に影響しないようにするパターンです。

サービス分割戦略

ドメイン駆動設計(DDD)アプローチ

境界コンテキストの特定

ECサイトドメイン例:
┌─────────────────┐  ┌─────────────────┐
│   ユーザー管理   │  │    商品カタログ  │
│   - 認証        │  │    - 商品情報    │
│   - プロフィール │  │    - カテゴリ    │
│   - 権限        │  │    - 在庫        │
└─────────────────┘  └─────────────────┘
┌─────────────────┐  ┌─────────────────┐
│     注文管理     │  │     支払い管理   │
│   - 注文作成    │  │    - 決済処理    │
│   - 注文履歴    │  │    - 返金処理    │
│   - ステータス  │  │    - 請求管理    │
└─────────────────┘  └─────────────────┘

データ分解戦略

正規化からの分解

-- モノリシック設計
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    product_id BIGINT,
    payment_id BIGINT,
    shipping_address TEXT,
    status VARCHAR(50),
    created_at TIMESTAMP
);

-- マイクロサービス分解後
-- 注文サービス
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    status VARCHAR(50),
    created_at TIMESTAMP
);

-- 注文項目サービス
CREATE TABLE order_items (
    id BIGINT PRIMARY KEY,
    order_id BIGINT,
    product_id BIGINT,
    quantity INTEGER,
    price DECIMAL
);

通信パターンとプロトコル

同期通信

REST API:

// 注文サービスから在庫サービスへの同期呼び出し
const checkInventory = async (productId, quantity) => {
    try {
        const response = await fetch(
            `${INVENTORY_SERVICE_URL}/inventory/${productId}/check`,
            {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ quantity })
            }
        );
        return await response.json();
    } catch (error) {
        // Circuit Breaker パターンの実装
        throw new ServiceUnavailableError('Inventory service unavailable');
    }
};

gRPC:

// inventory.proto
syntax = "proto3";

service InventoryService {
    rpc CheckAvailability(InventoryRequest) returns (InventoryResponse);
    rpc ReserveItems(ReservationRequest) returns (ReservationResponse);
}

message InventoryRequest {
    string product_id = 1;
    int32 quantity = 2;
}

message InventoryResponse {
    bool available = 1;
    int32 current_stock = 2;
}

非同期通信

メッセージキュー(RabbitMQ):

const amqp = require('amqplib');

// イベント発行者(注文サービス)
const publishOrderCreated = async (orderData) => {
    const connection = await amqp.connect('amqp://localhost');
    const channel = await connection.createChannel();
    
    const exchangeName = 'order.events';
    await channel.assertExchange(exchangeName, 'topic');
    
    const routingKey = 'order.created';
    const message = JSON.stringify({
        orderId: orderData.id,
        userId: orderData.userId,
        items: orderData.items,
        timestamp: new Date().toISOString()
    });
    
    await channel.publish(exchangeName, routingKey, Buffer.from(message));
    await connection.close();
};

// イベント購読者(在庫サービス)
const subscribeToOrderEvents = async () => {
    const connection = await amqp.connect('amqp://localhost');
    const channel = await connection.createChannel();
    
    const exchangeName = 'order.events';
    const queueName = 'inventory.order.queue';
    
    await channel.assertExchange(exchangeName, 'topic');
    await channel.assertQueue(queueName);
    await channel.bindQueue(queueName, exchangeName, 'order.created');
    
    await channel.consume(queueName, async (message) => {
        const orderData = JSON.parse(message.content.toString());
        await reserveInventory(orderData);
        channel.ack(message);
    });
};

Apache Kafka:

const kafka = require('kafkajs');

const client = kafka({
    clientId: 'order-service',
    brokers: ['localhost:9092']
});

// プロデューサー
const producer = client.producer();
await producer.send({
    topic: 'order-events',
    messages: [{
        partition: 0,
        key: `order-${orderId}`,
        value: JSON.stringify({
            eventType: 'ORDER_CREATED',
            orderId,
            userId,
            items,
            timestamp: Date.now()
        })
    }]
});

// コンシューマー
const consumer = client.consumer({ groupId: 'inventory-service-group' });
await consumer.subscribe({ topic: 'order-events' });
await consumer.run({
    eachMessage: async ({ topic, partition, message }) => {
        const event = JSON.parse(message.value.toString());
        if (event.eventType === 'ORDER_CREATED') {
            await handleOrderCreated(event);
        }
    }
});

データ管理戦略

イベントソーシング

// イベントストア実装例
class EventStore {
    constructor(database) {
        this.db = database;
    }
    
    async saveEvents(aggregateId, events, expectedVersion) {
        const transaction = await this.db.beginTransaction();
        try {
            for (const event of events) {
                await transaction.query(`
                    INSERT INTO events (aggregate_id, event_type, event_data, version)
                    VALUES (?, ?, ?, ?)
                `, [aggregateId, event.type, JSON.stringify(event.data), expectedVersion++]);
            }
            await transaction.commit();
        } catch (error) {
            await transaction.rollback();
            throw error;
        }
    }
    
    async getEvents(aggregateId, fromVersion = 0) {
        const result = await this.db.query(`
            SELECT event_type, event_data, version
            FROM events
            WHERE aggregate_id = ? AND version > ?
            ORDER BY version
        `, [aggregateId, fromVersion]);
        
        return result.map(row => ({
            type: row.event_type,
            data: JSON.parse(row.event_data),
            version: row.version
        }));
    }
}

// アグリゲート例
class Order {
    constructor() {
        this.id = null;
        this.items = [];
        this.status = 'PENDING';
        this.uncommittedEvents = [];
    }
    
    static fromEvents(events) {
        const order = new Order();
        events.forEach(event => order.apply(event));
        return order;
    }
    
    createOrder(orderId, userId, items) {
        this.raiseEvent({
            type: 'ORDER_CREATED',
            data: { orderId, userId, items }
        });
    }
    
    addItem(productId, quantity, price) {
        this.raiseEvent({
            type: 'ITEM_ADDED',
            data: { productId, quantity, price }
        });
    }
    
    apply(event) {
        switch (event.type) {
            case 'ORDER_CREATED':
                this.id = event.data.orderId;
                this.userId = event.data.userId;
                break;
            case 'ITEM_ADDED':
                this.items.push(event.data);
                break;
        }
    }
    
    raiseEvent(event) {
        this.apply(event);
        this.uncommittedEvents.push(event);
    }
}

CQRS(Command Query Responsibility Segregation)

// コマンド側(書き込み)
class OrderCommandHandler {
    constructor(eventStore, orderRepository) {
        this.eventStore = eventStore;
        this.orderRepository = orderRepository;
    }
    
    async handle(command) {
        switch (command.type) {
            case 'CREATE_ORDER':
                return await this.handleCreateOrder(command);
            case 'ADD_ITEM':
                return await this.handleAddItem(command);
        }
    }
    
    async handleCreateOrder(command) {
        const order = new Order();
        order.createOrder(command.orderId, command.userId, command.items);
        
        await this.eventStore.saveEvents(
            command.orderId,
            order.uncommittedEvents,
            0
        );
        
        return { success: true, orderId: command.orderId };
    }
}

// クエリ側(読み込み)
class OrderQueryHandler {
    constructor(readDatabase) {
        this.db = readDatabase;
    }
    
    async getOrderById(orderId) {
        const result = await this.db.query(`
            SELECT o.*, oi.product_id, oi.quantity, oi.price
            FROM orders_view o
            LEFT JOIN order_items_view oi ON o.id = oi.order_id
            WHERE o.id = ?
        `, [orderId]);
        
        return this.mapToOrderDto(result);
    }
    
    async getOrdersByUser(userId, page = 1, limit = 10) {
        const offset = (page - 1) * limit;
        const result = await this.db.query(`
            SELECT * FROM orders_view
            WHERE user_id = ?
            ORDER BY created_at DESC
            LIMIT ? OFFSET ?
        `, [userId, limit, offset]);
        
        return result.map(row => this.mapToOrderSummaryDto(row));
    }
}

// イベントハンドラー(読み込み用ビューの更新)
class OrderViewUpdater {
    constructor(database) {
        this.db = database;
    }
    
    async on(event) {
        switch (event.type) {
            case 'ORDER_CREATED':
                await this.createOrderView(event);
                break;
            case 'ITEM_ADDED':
                await this.addItemToView(event);
                break;
        }
    }
    
    async createOrderView(event) {
        await this.db.query(`
            INSERT INTO orders_view (id, user_id, status, created_at)
            VALUES (?, ?, 'PENDING', NOW())
        `, [event.data.orderId, event.data.userId]);
    }
}

インフラストラクチャとデプロイメント

Docker コンテナ化

マルチステージビルド例:

# ビルドステージ
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# 本番ステージ
FROM node:18-alpine AS production
WORKDIR /app

# セキュリティ: 非特権ユーザーの作成
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

# 依存関係とアプリケーションコードのコピー
COPY --from=builder /app/node_modules ./node_modules
COPY --chown=nextjs:nodejs . .

# ヘルスチェックの追加
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:3000/health || exit 1

USER nextjs
EXPOSE 3000

CMD ["npm", "start"]

Kubernetes デプロイメント

サービス定義:

# order-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  labels:
    app: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: order-service:v1.2.0
        ports:
        - containerPort: 3000
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: url
        - name: REDIS_URL
          valueFrom:
            configMapKeyRef:
              name: redis-config
              key: url
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
  - port: 80
    targetPort: 3000
  type: ClusterIP

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-service-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /orders
        pathType: Prefix
        backend:
          service:
            name: order-service
            port:
              number: 80

サービスメッシュ(Istio)

VirtualService設定:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: order-service-vs
spec:
  hosts:
  - order-service
  http:
  - match:
    - headers:
        canary:
          exact: "true"
    route:
    - destination:
        host: order-service
        subset: v2
      weight: 100
  - route:
    - destination:
        host: order-service
        subset: v1
      weight: 90
    - destination:
        host: order-service
        subset: v2
      weight: 10

---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: order-service-dr
spec:
  host: order-service
  trafficPolicy:
    circuitBreaker:
      consecutiveErrors: 3
      interval: 30s
      baseEjectionTime: 30s
      maxEjectionPercent: 50
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2

監視と可観測性

分散トレーシング(OpenTelemetry)

// tracing.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');

const jaegerExporter = new JaegerExporter({
    endpoint: 'http://jaeger:14268/api/traces',
});

const sdk = new NodeSDK({
    traceExporter: jaegerExporter,
    instrumentations: [getNodeAutoInstrumentations()]
});

sdk.start();

// カスタムスパンの作成
const { trace } = require('@opentelemetry/api');

const tracer = trace.getTracer('order-service', '1.0.0');

const processOrder = async (orderData) => {
    const span = tracer.startSpan('process_order');
    
    try {
        span.setAttributes({
            'order.id': orderData.id,
            'order.user_id': orderData.userId,
            'order.items_count': orderData.items.length
        });
        
        // ビジネスロジックの実行
        const result = await executeOrderProcessing(orderData);
        
        span.setStatus({ code: SpanStatusCode.OK });
        return result;
    } catch (error) {
        span.setStatus({
            code: SpanStatusCode.ERROR,
            message: error.message
        });
        throw error;
    } finally {
        span.end();
    }
};

メトリクス収集(Prometheus)

// metrics.js
const promClient = require('prom-client');

// カスタムメトリクスの定義
const httpRequestDuration = new promClient.Histogram({
    name: 'http_request_duration_seconds',
    help: 'Duration of HTTP requests in seconds',
    labelNames: ['method', 'route', 'status_code']
});

const orderProcessingCounter = new promClient.Counter({
    name: 'orders_processed_total',
    help: 'Total number of orders processed',
    labelNames: ['status']
});

const activeConnectionsGauge = new promClient.Gauge({
    name: 'active_connections',
    help: 'Number of active connections'
});

// Express ミドルウェア
const metricsMiddleware = (req, res, next) => {
    const startTime = Date.now();
    
    res.on('finish', () => {
        const duration = (Date.now() - startTime) / 1000;
        httpRequestDuration
            .labels(req.method, req.route?.path || req.path, res.statusCode)
            .observe(duration);
    });
    
    next();
};

// メトリクスエンドポイント
app.get('/metrics', async (req, res) => {
    res.set('Content-Type', promClient.register.contentType);
    res.end(await promClient.register.metrics());
});

ログ管理(構造化ログ)

// logger.js
const winston = require('winston');

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.errors({ stack: true }),
        winston.format.json()
    ),
    defaultMeta: {
        service: 'order-service',
        version: process.env.APP_VERSION || '1.0.0'
    },
    transports: [
        new winston.transports.Console({
            format: winston.format.combine(
                winston.format.colorize(),
                winston.format.simple()
            )
        }),
        new winston.transports.File({
            filename: 'error.log',
            level: 'error'
        }),
        new winston.transports.File({
            filename: 'combined.log'
        })
    ]
});

// 使用例
const createOrder = async (orderData) => {
    const correlationId = generateCorrelationId();
    
    logger.info('Order creation started', {
        correlationId,
        orderId: orderData.id,
        userId: orderData.userId,
        itemsCount: orderData.items.length
    });
    
    try {
        const result = await processOrder(orderData);
        
        logger.info('Order created successfully', {
            correlationId,
            orderId: result.id,
            processingTime: result.processingTime
        });
        
        return result;
    } catch (error) {
        logger.error('Order creation failed', {
            correlationId,
            orderId: orderData.id,
            error: error.message,
            stack: error.stack
        });
        throw error;
    }
};

メリット・デメリット

メリット

開発・運用面

  • 独立開発: チームが独立してサービスを開発・デプロイ可能
  • 技術多様性: サービスごとに最適な技術スタックを選択
  • 障害耐性: 単一サービスの障害が全体に波及しにくい
  • スケーラビリティ: 需要に応じて特定サービスのみスケール
  • 継続的デプロイメント: 小さな変更を頻繁にリリース可能

ビジネス面

  • 迅速な市場投入: 機能単位での独立したリリース
  • チーム自律性: ビジネス要件に応じた迅速な対応
  • イノベーション促進: 新技術の部分的導入が容易

デメリット

複雑性の増大

  • 分散システム固有の課題: ネットワーク分断、レイテンシ、データ整合性
  • 運用オーバーヘッド: 多数のサービスの監視・管理
  • デバッグ困難: 分散環境でのバグ特定・修正の複雑さ

初期投資

  • インフラストラクチャ: CI/CD、監視、ログ管理システムの構築
  • 学習コスト: チームのスキル習得と文化的変化
  • ツール導入: コンテナ、オーケストレーション、サービスメッシュ

データ管理の複雑さ

  • 分散トランザクション: ACIDプロパティの維持困難
  • データ一貫性: 結果整合性モデルの理解と実装
  • クエリの複雑化: 複数サービスをまたぐデータ取得

参考ページ

公式ドキュメント

技術リソース

書籍・記事

ツール・プラットフォーム

実装例

1. 簡単なマイクロサービス(Node.js + Express)

// package.json
{
  "name": "user-service",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "express": "^4.18.0",
    "mongoose": "^7.0.0",
    "cors": "^2.8.5",
    "helmet": "^6.0.0",
    "dotenv": "^16.0.0",
    "winston": "^3.8.0"
  }
}

// index.js
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const helmet = require('helmet');
const logger = require('./logger');

const app = express();
const PORT = process.env.PORT || 3000;

// ミドルウェア設定
app.use(helmet());
app.use(cors());
app.use(express.json());

// ログミドルウェア
app.use((req, res, next) => {
    logger.info(`${req.method} ${req.path}`, {
        ip: req.ip,
        userAgent: req.get('User-Agent')
    });
    next();
});

// MongoDB接続
mongoose.connect(process.env.MONGODB_URL, {
    useNewUrlParser: true,
    useUnifiedTopology: true
}).then(() => {
    logger.info('MongoDB connected successfully');
}).catch(err => {
    logger.error('MongoDB connection failed', { error: err.message });
    process.exit(1);
});

// ユーザーモデル
const userSchema = new mongoose.Schema({
    username: { type: String, required: true, unique: true },
    email: { type: String, required: true, unique: true },
    createdAt: { type: Date, default: Date.now }
});

const User = mongoose.model('User', userSchema);

// ヘルスチェックエンドポイント
app.get('/health', (req, res) => {
    res.json({
        status: 'healthy',
        timestamp: new Date().toISOString(),
        service: 'user-service',
        version: process.env.APP_VERSION || '1.0.0'
    });
});

// ユーザー作成
app.post('/users', async (req, res) => {
    try {
        const { username, email } = req.body;
        
        const user = new User({ username, email });
        await user.save();
        
        logger.info('User created', { userId: user._id, username });
        
        res.status(201).json({
            id: user._id,
            username: user.username,
            email: user.email,
            createdAt: user.createdAt
        });
    } catch (error) {
        logger.error('User creation failed', { 
            error: error.message,
            body: req.body 
        });
        
        if (error.code === 11000) {
            res.status(409).json({ error: 'Username or email already exists' });
        } else {
            res.status(500).json({ error: 'Internal server error' });
        }
    }
});

// ユーザー取得
app.get('/users/:id', async (req, res) => {
    try {
        const user = await User.findById(req.params.id);
        
        if (!user) {
            return res.status(404).json({ error: 'User not found' });
        }
        
        res.json({
            id: user._id,
            username: user.username,
            email: user.email,
            createdAt: user.createdAt
        });
    } catch (error) {
        logger.error('User retrieval failed', { 
            userId: req.params.id,
            error: error.message 
        });
        res.status(500).json({ error: 'Internal server error' });
    }
});

// サーバー起動
app.listen(PORT, () => {
    logger.info(`User service running on port ${PORT}`);
});

// グレースフルシャットダウン
process.on('SIGTERM', () => {
    logger.info('SIGTERM received, shutting down gracefully');
    mongoose.connection.close(() => {
        logger.info('MongoDB connection closed');
        process.exit(0);
    });
});

2. API Gateway(Node.js + Express)

// api-gateway/index.js
const express = require('express');
const httpProxy = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const jwt = require('jsonwebtoken');
const logger = require('./logger');

const app = express();
const PORT = process.env.PORT || 3001;

// レート制限設定
const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15分
    max: 100, // 最大100リクエスト
    message: 'Too many requests from this IP'
});

app.use(limiter);
app.use(express.json());

// 認証ミドルウェア
const authenticateToken = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];
    
    if (!token) {
        return res.status(401).json({ error: 'Access token required' });
    }
    
    jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
        if (err) {
            return res.status(403).json({ error: 'Invalid token' });
        }
        req.user = user;
        next();
    });
};

// サービス設定
const services = {
    user: process.env.USER_SERVICE_URL || 'http://localhost:3000',
    order: process.env.ORDER_SERVICE_URL || 'http://localhost:3002',
    inventory: process.env.INVENTORY_SERVICE_URL || 'http://localhost:3003'
};

// プロキシ設定
const createProxyMiddleware = (target, pathRewrite = {}) => {
    return httpProxy({
        target,
        changeOrigin: true,
        pathRewrite,
        onError: (err, req, res) => {
            logger.error('Proxy error', {
                target,
                path: req.path,
                error: err.message
            });
            res.status(502).json({ error: 'Service unavailable' });
        },
        onProxyReq: (proxyReq, req, res) => {
            logger.info('Proxy request', {
                target,
                method: req.method,
                path: req.path,
                userId: req.user?.id
            });
        }
    });
};

// ルーティング設定
app.use('/api/users', authenticateToken, createProxyMiddleware(services.user, {
    '^/api/users': '/users'
}));

app.use('/api/orders', authenticateToken, createProxyMiddleware(services.order, {
    '^/api/orders': '/orders'
}));

app.use('/api/inventory', authenticateToken, createProxyMiddleware(services.inventory, {
    '^/api/inventory': '/inventory'
}));

// ヘルスチェック(認証不要)
app.get('/health', (req, res) => {
    res.json({
        status: 'healthy',
        services: Object.keys(services),
        timestamp: new Date().toISOString()
    });
});

app.listen(PORT, () => {
    logger.info(`API Gateway running on port ${PORT}`);
});

3. Saga パターン実装(Choreography方式)

// saga/order-saga.js
const EventEmitter = require('events');

class OrderSagaOrchestrator extends EventEmitter {
    constructor(services) {
        super();
        this.services = services;
        this.sagaState = new Map();
        
        // イベントリスナー設定
        this.setupEventListeners();
    }
    
    setupEventListeners() {
        this.on('ORDER_CREATED', this.handleOrderCreated.bind(this));
        this.on('INVENTORY_RESERVED', this.handleInventoryReserved.bind(this));
        this.on('PAYMENT_PROCESSED', this.handlePaymentProcessed.bind(this));
        this.on('SAGA_COMPLETED', this.handleSagaCompleted.bind(this));
        
        // 失敗ケース
        this.on('INVENTORY_RESERVATION_FAILED', this.handleInventoryReservationFailed.bind(this));
        this.on('PAYMENT_FAILED', this.handlePaymentFailed.bind(this));
    }
    
    async startSaga(orderData) {
        const sagaId = this.generateSagaId();
        
        this.sagaState.set(sagaId, {
            orderId: orderData.id,
            status: 'STARTED',
            steps: [],
            createdAt: new Date()
        });
        
        try {
            // ステップ1: 注文作成
            await this.services.orderService.createOrder(orderData);
            this.updateSagaStep(sagaId, 'ORDER_CREATED', 'COMPLETED');
            this.emit('ORDER_CREATED', { sagaId, orderData });
            
        } catch (error) {
            this.updateSagaStep(sagaId, 'ORDER_CREATED', 'FAILED');
            await this.compensate(sagaId);
        }
    }
    
    async handleOrderCreated(event) {
        const { sagaId, orderData } = event;
        
        try {
            // ステップ2: 在庫予約
            await this.services.inventoryService.reserveItems(orderData.items);
            this.updateSagaStep(sagaId, 'INVENTORY_RESERVED', 'COMPLETED');
            this.emit('INVENTORY_RESERVED', { sagaId, orderData });
            
        } catch (error) {
            this.updateSagaStep(sagaId, 'INVENTORY_RESERVED', 'FAILED');
            this.emit('INVENTORY_RESERVATION_FAILED', { sagaId, orderData });
        }
    }
    
    async handleInventoryReserved(event) {
        const { sagaId, orderData } = event;
        
        try {
            // ステップ3: 支払い処理
            await this.services.paymentService.processPayment(orderData.payment);
            this.updateSagaStep(sagaId, 'PAYMENT_PROCESSED', 'COMPLETED');
            this.emit('PAYMENT_PROCESSED', { sagaId, orderData });
            
        } catch (error) {
            this.updateSagaStep(sagaId, 'PAYMENT_PROCESSED', 'FAILED');
            this.emit('PAYMENT_FAILED', { sagaId, orderData });
        }
    }
    
    async handlePaymentProcessed(event) {
        const { sagaId, orderData } = event;
        
        // ステップ4: 注文確定
        await this.services.orderService.confirmOrder(orderData.id);
        this.updateSagaStep(sagaId, 'ORDER_CONFIRMED', 'COMPLETED');
        this.emit('SAGA_COMPLETED', { sagaId, orderData });
    }
    
    async handleInventoryReservationFailed(event) {
        const { sagaId, orderData } = event;
        await this.compensate(sagaId);
    }
    
    async handlePaymentFailed(event) {
        const { sagaId, orderData } = event;
        await this.compensate(sagaId);
    }
    
    async compensate(sagaId) {
        const saga = this.sagaState.get(sagaId);
        const completedSteps = saga.steps.filter(step => step.status === 'COMPLETED');
        
        // 逆順で補償アクション実行
        for (const step of completedSteps.reverse()) {
            switch (step.name) {
                case 'PAYMENT_PROCESSED':
                    await this.services.paymentService.refund(saga.orderId);
                    break;
                case 'INVENTORY_RESERVED':
                    await this.services.inventoryService.releaseReservation(saga.orderId);
                    break;
                case 'ORDER_CREATED':
                    await this.services.orderService.cancelOrder(saga.orderId);
                    break;
            }
        }
        
        saga.status = 'COMPENSATED';
        this.sagaState.set(sagaId, saga);
    }
    
    updateSagaStep(sagaId, stepName, status) {
        const saga = this.sagaState.get(sagaId);
        saga.steps.push({
            name: stepName,
            status: status,
            timestamp: new Date()
        });
        this.sagaState.set(sagaId, saga);
    }
    
    generateSagaId() {
        return `saga_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    }
}

module.exports = OrderSagaOrchestrator;

4. Circuit Breaker パターン実装

// circuit-breaker.js
class CircuitBreaker {
    constructor(service, options = {}) {
        this.service = service;
        this.failureThreshold = options.failureThreshold || 5;
        this.recoveryTimeout = options.recoveryTimeout || 60000; // 60秒
        this.monitoringPeriod = options.monitoringPeriod || 10000; // 10秒
        
        this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
        this.failureCount = 0;
        this.nextAttempt = Date.now();
        this.requestCount = 0;
        this.successCount = 0;
    }
    
    async call(method, ...args) {
        const currentTime = Date.now();
        
        if (this.state === 'OPEN') {
            if (currentTime < this.nextAttempt) {
                throw new Error('Circuit breaker is OPEN');
            } else {
                this.state = 'HALF_OPEN';
                this.failureCount = 0;
            }
        }
        
        try {
            const result = await this.service[method](...args);
            this.onSuccess();
            return result;
        } catch (error) {
            this.onFailure();
            throw error;
        }
    }
    
    onSuccess() {
        this.failureCount = 0;
        this.successCount++;
        
        if (this.state === 'HALF_OPEN') {
            this.state = 'CLOSED';
        }
    }
    
    onFailure() {
        this.failureCount++;
        
        if (this.failureCount >= this.failureThreshold) {
            this.state = 'OPEN';
            this.nextAttempt = Date.now() + this.recoveryTimeout;
        }
    }
    
    getStats() {
        return {
            state: this.state,
            failureCount: this.failureCount,
            successCount: this.successCount,
            requestCount: this.requestCount,
            nextAttempt: this.nextAttempt
        };
    }
}

// 使用例
class InventoryService {
    async checkAvailability(productId, quantity) {
        // 外部APIコール(失敗する可能性あり)
        const response = await fetch(`${this.baseUrl}/check/${productId}`, {
            method: 'POST',
            body: JSON.stringify({ quantity }),
            headers: { 'Content-Type': 'application/json' }
        });
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        return response.json();
    }
}

const inventoryService = new InventoryService();
const inventoryCircuitBreaker = new CircuitBreaker(inventoryService, {
    failureThreshold: 3,
    recoveryTimeout: 30000
});

// Circuit Breaker経由での呼び出し
const checkInventoryWithCircuitBreaker = async (productId, quantity) => {
    try {
        return await inventoryCircuitBreaker.call('checkAvailability', productId, quantity);
    } catch (error) {
        if (error.message === 'Circuit breaker is OPEN') {
            // フォールバック処理
            return { available: false, reason: 'Service temporarily unavailable' };
        }
        throw error;
    }
};

5. Docker Compose 設定例

# docker-compose.yml
version: '3.8'

services:
  # API Gateway
  api-gateway:
    build: ./api-gateway
    ports:
      - "3001:3001"
    environment:
      - USER_SERVICE_URL=http://user-service:3000
      - ORDER_SERVICE_URL=http://order-service:3002
      - INVENTORY_SERVICE_URL=http://inventory-service:3003
      - JWT_SECRET=your-secret-key
    depends_on:
      - user-service
      - order-service
      - inventory-service
    networks:
      - microservices

  # User Service
  user-service:
    build: ./user-service
    environment:
      - MONGODB_URL=mongodb://mongo:27017/userdb
      - PORT=3000
    depends_on:
      - mongo
    networks:
      - microservices

  # Order Service
  order-service:
    build: ./order-service
    environment:
      - MONGODB_URL=mongodb://mongo:27017/orderdb
      - REDIS_URL=redis://redis:6379
      - RABBITMQ_URL=amqp://rabbitmq:5672
      - PORT=3002
    depends_on:
      - mongo
      - redis
      - rabbitmq
    networks:
      - microservices

  # Inventory Service
  inventory-service:
    build: ./inventory-service
    environment:
      - POSTGRES_URL=postgresql://postgres:password@postgres:5432/inventorydb
      - REDIS_URL=redis://redis:6379
      - PORT=3003
    depends_on:
      - postgres
      - redis
    networks:
      - microservices

  # MongoDB
  mongo:
    image: mongo:5.0
    environment:
      - MONGO_INITDB_ROOT_USERNAME=admin
      - MONGO_INITDB_ROOT_PASSWORD=password
    volumes:
      - mongo_data:/data/db
    networks:
      - microservices

  # PostgreSQL
  postgres:
    image: postgres:14
    environment:
      - POSTGRES_DB=inventorydb
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - microservices

  # Redis
  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    networks:
      - microservices

  # RabbitMQ
  rabbitmq:
    image: rabbitmq:3.9-management
    environment:
      - RABBITMQ_DEFAULT_USER=admin
      - RABBITMQ_DEFAULT_PASS=password
    ports:
      - "15672:15672"  # Management UI
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    networks:
      - microservices

  # Jaeger (分散トレーシング)
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"  # UI
      - "14268:14268"  # HTTP
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    networks:
      - microservices

  # Prometheus (メトリクス収集)
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    networks:
      - microservices

  # Grafana (ダッシュボード)
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - microservices

volumes:
  mongo_data:
  postgres_data:
  redis_data:
  rabbitmq_data:
  grafana_data:

networks:
  microservices:
    driver: bridge

この包括的なマイクロサービスアーキテクチャガイドにより、理論から実践まで幅広くマイクロサービスの実装方法を理解できます。適切な設計とツールの選択により、スケーラブルで保守性の高い分散システムを構築することが可能です。