メインコンテンツにスキップ

🧩 マイクロサービスアーキテクチャ

📖 定義

マイクロサービスアーキテクチャ(MSA、Microservices Architecture)は、1つの大きなアプリケーションを複数の小さく独立したサービスに分割して開発・デプロイするアーキテクチャパターンです。各サービスは特定のビジネス機能を担当し、独立してデプロイ・スケールできます。モノリシック(Monolithic)アーキテクチャとは異なり、サービス間の疎結合により柔軟性と拡張性を提供します。

🎯 比喩で理解する

大企業 vs スタートアップ連合

モノリシック = 大企業
├─ すべての部門が1つの建物に
├─ 中央集権的管理
├─ 1つの部門の問題 → 全体に影響
├─ 変更が困難
└─ 意思決定が遅い

マイクロサービス = スタートアップ連合
├─ 各チームが独立したオフィス
├─ 自律的な意思決定
├─ 1つのチームの問題 → 他のチームは正常動作
├─ 素早い変更
└─ 柔軟な拡張

レゴ vs 粘土

モノリシック = 粘土の塊
┌──────────────────────────────┐
│ ユーザー │ 商品 │ 注文 │
│ 管理 │ 管理 │ 管理 │
│ すべて1つに │
└──────────────────────────────┘
- 全体を作り直す必要がある
- 1つの部分を修正 → 全体に影響
- 拡張が困難

マイクロサービス = レゴブロック
┌─────┐ ┌─────┐ ┌─────┐
│ユーザー│ │ 商品│ │ 注文│
│サービス│ │サービス│ │サービス│
└─────┘ └─────┘ └─────┘
- ブロック交換が簡単
- 独立して修正
- 必要な部分のみ拡張

⚙️ 動作原理

1. モノリシック vs マイクロサービス

========== モノリシック ==========
┌─────────────────────────────────────┐
│ 1つのアプリケーション │
│ │
│ ┌─────────────────────────────┐ │
│ │ ユーザー管理モジュール │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ 商品管理モジュール │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ 注文管理モジュール │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ 決済モジュール │ │
│ └─────────────────────────────┘ │
│ │
│ 1つのデータベース │
│ 1つのコードベース │
│ 1つのデプロイ単位 │
└─────────────────────────────────────┘

メリット:
✅ 開発初期が速い
✅ テストがシンプル
✅ デプロイがシンプル(1つのみ)
✅ デバッグが簡単

デメリット:
❌ 規模が大きくなると複雑
❌ デプロイ時に全体停止
❌ 部分的な拡張不可
❌ 技術スタック変更が困難

========== マイクロサービス ==========
┌──────────┐ ┌──────────┐ ┌──────────┐
│ユーザー │ │商品 │ │注文 │
│サービス │ │サービス │ │サービス │
│ │ │ │ │ │
│Node.js │ │Java │ │Go │
│MongoDB │ │MySQL │ │PostgreSQL│
└──────────┘ └──────────┘ └──────────┘
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│決済 │ │通知 │ │レビュー │
│サービス │ │サービス │ │サービス │
│ │ │ │ │ │
│Python │ │Node.js │ │Ruby │
│Redis │ │Kafka │ │Cassandra │
└──────────┘ └──────────┘ └──────────┘

メリット:
✅ 独立したデプロイ
✅ 技術スタックの自由
✅ 部分的な拡張可能
✅ チームの独立性
✅ 障害の分離

デメリット:
❌ 初期の複雑度が高い
❌ ネットワーク通信のオーバーヘッド
❌ 分散トランザクションが困難
❌ テストが複雑
❌ 運用コスト増加

2. サービス間通信

========== 同期通信 (HTTP/REST) ==========
注文サービス → 商品サービス
"商品123の在庫はありますか?"

"はい、5個あります"

注文サービス → 決済サービス
"10,000円の決済をお願いします"

"決済完了"

注文完了

メリット: シンプル、直感的
デメリット: 1つのサービス障害時に全体が失敗

========== 非同期通信 (メッセージキュー) ==========
注文サービス → Message Queue
"注文が作成されました" メッセージを発行

┌──────────────────┐
│ Message Queue │
│ (RabbitMQ, │
│ Kafka など) │
└──────────────────┘

┌────┴────┬────────┐
↓ ↓ ↓
決済 通知 在庫
サービス サービス サービス
それぞれ独立して処理

メリット: 疎結合、障害の分離
デメリット: 複雑度の増加、デバッグが困難

========== API Gateway ==========
クライアント (モバイル/Web)

┌──────────────────┐
│ API Gateway │
│ - ルーティング │
│ - 認証 │
│ - ロードバランシング│
│ - ロギング │
└──────────────────┘
┌────┴────┬────────┐
↓ ↓ ↓
サービスA サービスB サービスC

役割:
- 単一のエントリーポイント
- クライアントの簡素化
- 共通機能の処理

3. データ管理

========== モノリシック: 共有データベース ==========
┌─────────────────────────────────────┐
│ アプリケーション │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │モジュールA│ │モジュールB│ │モジュールC│ │
│ └──┬──┘ └──┬──┘ └──┬──┘ │
└─────┼────────┼───────┼──────────────┘
└────────┼───────┘

┌──────────────────┐
│ 1つのデータベース │
│ ┌────┬────┬────┐│
│ │TA│TB│TC││
│ └────┴────┴────┘│
└──────────────────┘

メリット: JOIN が簡単、一貫性を保証
デメリット: 結合度が高い、拡張が困難

========== マイクロサービス: DB per Service ==========
┌──────────┐ ┌──────────┐ ┌──────────┐
│サービス A │ │サービス B │ │サービス C │
└────┬─────┘ └────┬─────┘ └────┬─────┘
↓ ↓ ↓
┌─────────┐ ┌─────────┐ ┌─────────┐
│ DB A │ │ DB B │ │ DB C │
│ (MySQL) │ │(MongoDB)│ │(PostgreSQL)│
└─────────┘ └─────────┘ └─────────┘

メリット: 独立性、技術選択の自由
デメリット: JOIN 不可、一貫性が困難

========== データの一貫性 ==========
// Saga パターン
1. 注文サービス: 注文を作成
2. 決済サービス: 決済を処理
3. 在庫サービス: 在庫を減らす
4. 配送サービス: 配送を開始

もし3段階で失敗したら?
→ 補償トランザクション (Compensating Transaction)
4. 在庫減少失敗
3. 決済キャンセル ← 補償
2. 注文キャンセル ← 補償

💡 実際の例

モノリシックの例 (Express.js)

// ========== モノリシックアプリケーション ==========
// server.js - 1つのファイルにすべての機能

const express = require('express');
const app = express();

app.use(express.json());

// 1つのデータベース
const db = require('./database');

// ========== ユーザー管理 ==========
app.post('/api/users', async (req, res) => {
const { username, email, password } = req.body;
const user = await db.users.create({ username, email, password });
res.json(user);
});

app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json(user);
});

// ========== 商品管理 ==========
app.post('/api/products', async (req, res) => {
const { name, price, stock } = req.body;
const product = await db.products.create({ name, price, stock });
res.json(product);
});

app.get('/api/products', async (req, res) => {
const products = await db.products.findAll();
res.json(products);
});

// ========== 注文管理 ==========
app.post('/api/orders', async (req, res) => {
const { userId, productId, quantity } = req.body;

// トランザクションで一貫性を保証
const transaction = await db.sequelize.transaction();

try {
// 1. 在庫確認
const product = await db.products.findById(productId, { transaction });
if (product.stock < quantity) {
throw new Error('在庫不足');
}

// 2. 在庫を減らす
await product.update(
{ stock: product.stock - quantity },
{ transaction }
);

// 3. 注文作成
const order = await db.orders.create(
{ userId, productId, quantity, total: product.price * quantity },
{ transaction }
);

// 4. 決済処理
await processPayment(order.total);

await transaction.commit();
res.json(order);
} catch (error) {
await transaction.rollback();
res.status(400).json({ error: error.message });
}
});

// ========== 決済処理 ==========
app.post('/api/payments', async (req, res) => {
const { orderId, amount } = req.body;
const payment = await db.payments.create({ orderId, amount });
res.json(payment);
});

// 1つのサーバーで実行
app.listen(3000, () => {
console.log('モノリシックサーバー実行中: http://localhost:3000');
});

/*
メリット:
- コードが1か所に
- 開発が速い
- デバッグが簡単
- トランザクションがシンプル

デメリット:
- 規模が大きくなると複雑
- デプロイ時に全体再起動
- 部分的な拡張不可
- 1つの機能障害 → 全体に影響
*/

マイクロサービスの例

// ========== 1. ユーザーサービス (user-service.js) ==========
// ポート: 3001
const express = require('express');
const app = express();
const mongoose = require('mongoose');

app.use(express.json());

// 独立したデータベース
mongoose.connect('mongodb://localhost/users-db');

const User = mongoose.model('User', {
username: String,
email: String,
password: String
});

// ユーザー作成
app.post('/users', async (req, res) => {
const { username, email, password } = req.body;

try {
const user = new User({ username, email, password });
await user.save();

// イベント発行(他のサービスに通知)
await publishEvent('user.created', { userId: user._id, email });

res.json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});

// ユーザー取得
app.get('/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
});

app.listen(3001, () => {
console.log('ユーザーサービス: http://localhost:3001');
});

// ========== 2. 商品サービス (product-service.js) ==========
// ポート: 3002
const express = require('express');
const app = express();
const { Pool } = require('pg');

app.use(express.json());

// PostgreSQL を使用(異なるDB!)
const pool = new Pool({
host: 'localhost',
database: 'products-db',
port: 5432
});

// 商品リスト
app.get('/products', async (req, res) => {
const result = await pool.query('SELECT * FROM products');
res.json(result.rows);
});

// 商品詳細
app.get('/products/:id', async (req, res) => {
const result = await pool.query(
'SELECT * FROM products WHERE id = $1',
[req.params.id]
);
res.json(result.rows[0]);
});

// 在庫確認
app.get('/products/:id/stock', async (req, res) => {
const result = await pool.query(
'SELECT stock FROM products WHERE id = $1',
[req.params.id]
);
res.json({ stock: result.rows[0].stock });
});

// 在庫減少
app.post('/products/:id/decrease-stock', async (req, res) => {
const { quantity } = req.body;

const client = await pool.connect();
try {
await client.query('BEGIN');

// 現在の在庫確認
const result = await client.query(
'SELECT stock FROM products WHERE id = $1 FOR UPDATE',
[req.params.id]
);

const currentStock = result.rows[0].stock;
if (currentStock < quantity) {
throw new Error('在庫不足');
}

// 在庫減少
await client.query(
'UPDATE products SET stock = stock - $1 WHERE id = $2',
[quantity, req.params.id]
);

await client.query('COMMIT');
res.json({ success: true });
} catch (error) {
await client.query('ROLLBACK');
res.status(400).json({ error: error.message });
} finally {
client.release();
}
});

app.listen(3002, () => {
console.log('商品サービス: http://localhost:3002');
});

// ========== 3. 注文サービス (order-service.js) ==========
// ポート: 3003
const express = require('express');
const axios = require('axios');
const app = express();

app.use(express.json());

const orders = []; // 実際にはデータベースを使用

// 注文作成
app.post('/orders', async (req, res) => {
const { userId, productId, quantity } = req.body;

try {
// 1. ユーザー確認(ユーザーサービスを呼び出し)
const userResponse = await axios.get(
`http://localhost:3001/users/${userId}`
);
const user = userResponse.data;

if (!user) {
return res.status(404).json({ error: 'ユーザーが見つかりません' });
}

// 2. 商品情報取得(商品サービスを呼び出し)
const productResponse = await axios.get(
`http://localhost:3002/products/${productId}`
);
const product = productResponse.data;

// 3. 在庫確認
const stockResponse = await axios.get(
`http://localhost:3002/products/${productId}/stock`
);
const { stock } = stockResponse.data;

if (stock < quantity) {
return res.status(400).json({ error: '在庫が不足しています' });
}

// 4. 在庫減少
await axios.post(
`http://localhost:3002/products/${productId}/decrease-stock`,
{ quantity }
);

// 5. 決済処理(決済サービスを呼び出し)
const total = product.price * quantity;
const paymentResponse = await axios.post(
'http://localhost:3004/payments',
{ userId, amount: total }
);

// 6. 注文作成
const order = {
id: orders.length + 1,
userId,
productId,
quantity,
total,
status: 'completed',
createdAt: new Date()
};
orders.push(order);

// 7. イベント発行
await publishEvent('order.created', order);

res.json(order);
} catch (error) {
// Saga パターン: 補償トランザクション
console.error('注文失敗:', error.message);

// 在庫復旧
try {
await axios.post(
`http://localhost:3002/products/${productId}/increase-stock`,
{ quantity }
);
} catch (rollbackError) {
console.error('在庫復旧失敗:', rollbackError.message);
}

res.status(500).json({ error: '注文処理失敗' });
}
});

// 注文取得
app.get('/orders/:id', (req, res) => {
const order = orders.find(o => o.id === parseInt(req.params.id));
res.json(order);
});

app.listen(3003, () => {
console.log('注文サービス: http://localhost:3003');
});

// ========== 4. 決済サービス (payment-service.js) ==========
// ポート: 3004
const express = require('express');
const app = express();

app.use(express.json());

const payments = [];

app.post('/payments', async (req, res) => {
const { userId, amount } = req.body;

// 外部決済APIを呼び出し(例: Stripe, Toss Payments)
try {
// 実際の決済処理
const payment = {
id: payments.length + 1,
userId,
amount,
status: 'success',
createdAt: new Date()
};
payments.push(payment);

// イベント発行
await publishEvent('payment.completed', payment);

res.json(payment);
} catch (error) {
res.status(400).json({ error: '決済失敗' });
}
});

app.listen(3004, () => {
console.log('決済サービス: http://localhost:3004');
});

// ========== 5. API Gateway (gateway.js) ==========
// ポート: 3000
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();

// 認証ミドルウェア
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: '認証が必要です' });
}
// JWT検証など
next();
}

// ロギングミドルウェア
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});

// ユーザーサービスプロキシ
app.use('/api/users', authenticate, createProxyMiddleware({
target: 'http://localhost:3001',
pathRewrite: { '^/api/users': '/users' },
changeOrigin: true
}));

// 商品サービスプロキシ
app.use('/api/products', createProxyMiddleware({
target: 'http://localhost:3002',
pathRewrite: { '^/api/products': '/products' },
changeOrigin: true
}));

// 注文サービスプロキシ
app.use('/api/orders', authenticate, createProxyMiddleware({
target: 'http://localhost:3003',
pathRewrite: { '^/api/orders': '/orders' },
changeOrigin: true
}));

// 決済サービスプロキシ
app.use('/api/payments', authenticate, createProxyMiddleware({
target: 'http://localhost:3004',
pathRewrite: { '^/api/payments': '/payments' },
changeOrigin: true
}));

app.listen(3000, () => {
console.log('API Gateway: http://localhost:3000');
});

// ========== 6. イベントバス (event-bus.js) ==========
const amqp = require('amqplib');

let connection, channel;

// RabbitMQ 接続
async function connect() {
connection = await amqp.connect('amqp://localhost');
channel = await connection.createChannel();
}

// イベント発行
async function publishEvent(eventType, data) {
await channel.assertQueue(eventType);
channel.sendToQueue(
eventType,
Buffer.from(JSON.stringify(data))
);
console.log(`イベント発行: ${eventType}`, data);
}

// イベント購読
async function subscribeEvent(eventType, callback) {
await channel.assertQueue(eventType);
channel.consume(eventType, (msg) => {
const data = JSON.parse(msg.content.toString());
console.log(`イベント受信: ${eventType}`, data);
callback(data);
channel.ack(msg);
});
}

connect();

module.exports = { publishEvent, subscribeEvent };

Docker Compose でマイクロサービスを実行

# docker-compose.yml
version: '3.8'

services:
# API Gateway
gateway:
build: ./gateway
ports:
- "3000:3000"
depends_on:
- user-service
- product-service
- order-service
- payment-service

# ユーザーサービス
user-service:
build: ./user-service
ports:
- "3001:3001"
environment:
- MONGO_URL=mongodb://mongo:27017/users
depends_on:
- mongo
- rabbitmq

# 商品サービス
product-service:
build: ./product-service
ports:
- "3002:3002"
environment:
- POSTGRES_URL=postgres://postgres:password@postgres:5432/products
depends_on:
- postgres
- rabbitmq

# 注文サービス
order-service:
build: ./order-service
ports:
- "3003:3003"
environment:
- MYSQL_URL=mysql://root:password@mysql:3306/orders
depends_on:
- mysql
- rabbitmq

# 決済サービス
payment-service:
build: ./payment-service
ports:
- "3004:3004"
depends_on:
- rabbitmq

# データベース
mongo:
image: mongo:6
volumes:
- mongo-data:/data/db

postgres:
image: postgres:15
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=products
volumes:
- postgres-data:/var/lib/postgresql/data

mysql:
image: mysql:8
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=orders
volumes:
- mysql-data:/var/lib/mysql

# メッセージキュー
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672" # 管理UI

volumes:
mongo-data:
postgres-data:
mysql-data:

Service Mesh (Istio の例)

# istio-config.yaml
# サービスメッシュ - サービス間通信の管理

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
# トラフィック分散(カナリアデプロイ)
- match:
- headers:
user-type:
exact: beta
route:
- destination:
host: order-service
subset: v2 # 新しいバージョン
weight: 20
- destination:
host: order-service
subset: v1 # 既存のバージョン
weight: 80

# リトライポリシー
- route:
- destination:
host: order-service
retries:
attempts: 3
perTryTimeout: 2s

# タイムアウト
timeout: 10s

---
# Circuit Breaker
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: payment-service
spec:
host: payment-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50

🤔 よくある質問

Q1. いつマイクロサービスを使用すべきですか?

A:

✅ マイクロサービスが適している場合:

1. 規模の大きいアプリケーション
- チーム: 10人以上
- コード: 10万行以上
- ユーザー: 数十万人以上

2. 迅速なデプロイが必要
- 1日に複数回デプロイ
- 独立した機能のリリース
- A/Bテストを頻繁に実施

3. 多様な技術スタックが必要
- サービスごとに最適な技術を選択
- レガシーシステムとの統合

4. 独立した拡張が必要
- 特定の機能のみトラフィックが多い
- サービスごとにリソース要件が異なる

5. チームの独立性が重要
- 複数のチームが同時開発
- チーム間の依存性を最小化

例:
- Netflix: 数百のマイクロサービス
- Amazon: 2-pizza team (チームごとにサービスを担当)
- Uber: 地域別、機能別にサービスを分離

❌ モノリシックが適している場合:

1. 小規模なアプリケーション
- チーム: 5人以下
- 機能: 明確でシンプル
- トラフィック: 少ない

2. 初期スタートアップ
- 迅速なMVP開発が必要
- 要件が頻繁に変更
- リソースが限定的

3. シンプルなCRUD
- 複雑なビジネスロジックがない
- サービス間の境界が不明確

4. 運用経験不足
- DevOps チームがない
- 分散システムの経験がない

例:
- ブログ、ポートフォリオ
- 小規模電子商取引
- 社内ツール

📊 意思決定チェックリスト:

□ チーム規模10人以上?
□ コードベース10万行以上?
□ 独立したデプロイが頻繁に必要?
□ 部分的な拡張が必要?
□ DevOps チームがある?
□ 分散システムの経験がある?

3つ以上チェック → マイクロサービスを検討
2つ以下 → モノリシックを維持

Q2. マイクロサービスの最大の課題は?

A:

// ========== 1. 分散トランザクション ==========

// モノリシック: シンプルなトランザクション
await db.transaction(async (t) => {
await createOrder(data, t);
await decreaseStock(productId, t);
await processPayment(amount, t);
// 1つでも失敗すると全体がロールバック
});

// マイクロサービス: 複雑な Saga パターン
async function createOrderSaga(data) {
try {
// ステップ1
const order = await orderService.create(data);

// ステップ2
await productService.decreaseStock(data.productId);

// ステップ3
await paymentService.process(order.total);

return order;
} catch (error) {
// 補償トランザクション(逆順で)
await paymentService.refund(order.total);
await productService.increaseStock(data.productId);
await orderService.cancel(order.id);

throw error;
}
}

// ========== 2. データの一貫性 ==========

// 問題: 複数のサービスに分散したデータ
// ユーザーサービス: userId, name
// 注文サービス: userId, orders
// 決済サービス: userId, payments

// 解決策1: イベントソーシング
eventBus.on('user.updated', async (event) => {
// ユーザー情報が変更されたら他のサービスも更新
await orderService.updateUserInfo(event.userId, event.name);
await paymentService.updateUserInfo(event.userId, event.name);
});

// 解決策2: CQRS (Command Query Responsibility Segregation)
// 書き込みと読み取りを分離
// 書き込み: 各サービス独立
// 読み取り: 統合されたビュー(Read Model)

// ========== 3. ネットワーク遅延 ==========

// モノリシック: 関数呼び出し(速い)
const user = getUser(userId); // 1ms

// マイクロサービス: HTTPリクエスト(遅い)
const user = await axios.get(`http://user-service/users/${userId}`); // 50ms

// 解決策: キャッシング
const redis = require('redis');
const cache = redis.createClient();

async function getUser(userId) {
// 1. キャッシュ確認
const cached = await cache.get(`user:${userId}`);
if (cached) return JSON.parse(cached);

// 2. サービス呼び出し
const response = await axios.get(`http://user-service/users/${userId}`);
const user = response.data;

// 3. キャッシュに保存
await cache.setex(`user:${userId}`, 3600, JSON.stringify(user));

return user;
}

// ========== 4. サービス障害処理 ==========

// Circuit Breaker パターン
const CircuitBreaker = require('opossum');

const options = {
timeout: 3000, // 3秒タイムアウト
errorThresholdPercentage: 50, // 50%失敗時
resetTimeout: 30000 // 30秒後に再試行
};

const breaker = new CircuitBreaker(async (userId) => {
return await axios.get(`http://user-service/users/${userId}`);
}, options);

breaker.fallback(() => ({
id: userId,
name: '不明', // フォールバックデータ
cached: true
}));

// 使用
breaker.fire(userId)
.then(console.log)
.catch(console.error);

// ========== 5. 監視とデバッグ ==========

// 分散トレーシング
// Jaeger、Zipkin を使用

const tracer = require('jaeger-client').initTracer(config);

app.use((req, res, next) => {
const span = tracer.startSpan('http_request');
span.setTag('http.method', req.method);
span.setTag('http.url', req.url);

req.span = span;
next();
});

// サービス間呼び出し時にトレースIDを渡す
await axios.get('http://order-service/orders', {
headers: {
'x-trace-id': req.span.context().toTraceId()
}
});

Q3. API Gateway の役割は?

A:

// ========== API Gateway の主な機能 ==========

const express = require('express');
const rateLimit = require('express-rate-limit');
const jwt = require('jsonwebtoken');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

// ========== 1. ルーティング ==========
// クライアントは1つのエンドポイントだけを知っていればいい
app.use('/api/users', createProxyMiddleware({
target: 'http://user-service:3001',
changeOrigin: true
}));

app.use('/api/products', createProxyMiddleware({
target: 'http://product-service:3002',
changeOrigin: true
}));

// ========== 2. 認証と認可 ==========
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];

if (!token) {
return res.status(401).json({ error: 'トークンが必要です' });
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: '無効なトークン' });
}
}

app.use('/api/orders', authenticate, createProxyMiddleware({
target: 'http://order-service:3003'
}));

// ========== 3. Rate Limiting ==========
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 100 // 最大100リクエスト
});

app.use('/api/', limiter);

// ========== 4. ロードバランシング ==========
const productServiceInstances = [
'http://product-service-1:3002',
'http://product-service-2:3002',
'http://product-service-3:3002'
];

let currentIndex = 0;

app.use('/api/products', createProxyMiddleware({
target: productServiceInstances[currentIndex],
router: () => {
// ラウンドロビン
const target = productServiceInstances[currentIndex];
currentIndex = (currentIndex + 1) % productServiceInstances.length;
return target;
}
}));

// ========== 5. リクエスト/レスポンス変換 ==========
app.use('/api/legacy', createProxyMiddleware({
target: 'http://legacy-service:8080',
onProxyReq: (proxyReq, req) => {
// リクエスト変換
proxyReq.setHeader('X-API-Version', '2.0');
},
onProxyRes: (proxyRes, req, res) => {
// レスポンス変換
proxyRes.headers['X-Custom-Header'] = 'Gateway';
}
}));

// ========== 6. キャッシング ==========
const redis = require('redis');
const cache = redis.createClient();

app.get('/api/products/:id', async (req, res) => {
const cacheKey = `product:${req.params.id}`;

// キャッシュ確認
const cached = await cache.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}

// サービス呼び出し
const response = await axios.get(
`http://product-service:3002/products/${req.params.id}`
);

// キャッシュに保存
await cache.setex(cacheKey, 3600, JSON.stringify(response.data));

res.json(response.data);
});

// ========== 7. ロギングと監視 ==========
app.use((req, res, next) => {
const start = Date.now();

res.on('finish', () => {
const duration = Date.now() - start;
console.log({
method: req.method,
path: req.path,
status: res.statusCode,
duration: `${duration}ms`,
user: req.user?.id
});
});

next();
});

// ========== 8. エラー処理 ==========
app.use((err, req, res, next) => {
console.error('Gateway Error:', err);

if (err.code === 'ECONNREFUSED') {
return res.status(503).json({
error: 'サービスを利用できません'
});
}

res.status(500).json({
error: 'サーバーエラーが発生しました'
});
});

// ========== 9. サービスディスカバリー ==========
const consul = require('consul')();

async function getServiceUrl(serviceName) {
const result = await consul.health.service({
service: serviceName,
passing: true // ヘルスチェック通過したインスタンスのみ
});

if (result.length === 0) {
throw new Error(`${serviceName} サービスが見つかりません`);
}

// ランダムに選択
const instance = result[Math.floor(Math.random() * result.length)];
return `http://${instance.Service.Address}:${instance.Service.Port}`;
}

app.listen(3000);

Q4. マイクロサービスのデプロイ戦略は?

A:

# ========== 1. Blue-Green デプロイ ==========
# 新しいバージョン(Green)をデプロイしてトラフィックを一度に切り替え

# Blue (現在のバージョン)
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
version: blue # 現在のトラフィック
ports:
- port: 80

---
# Green (新しいバージョン)をデプロイ
kubectl apply -f order-service-green.yaml

# テスト後にトラフィック切り替え
kubectl patch service order-service -p '{"spec":{"selector":{"version":"green"}}}'

# 問題があればすぐにロールバック
kubectl patch service order-service -p '{"spec":{"selector":{"version":"blue"}}}'

# ========== 2. カナリアデプロイ ==========
# 一部のトラフィックのみ新しいバージョンに送り、徐々に拡大

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
- route:
- destination:
host: order-service
subset: v1 # 既存のバージョン
weight: 90 # 90%のトラフィック
- destination:
host: order-service
subset: v2 # 新しいバージョン
weight: 10 # 10%のトラフィック

# 徐々に増加
# 10% → 25% → 50% → 75% → 100%

# ========== 3. ローリングアップデート ==========
# Kubernetes のデフォルト戦略

apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 最大1つ追加作成
maxUnavailable: 1 # 最大1つ停止許可
template:
spec:
containers:
- name: order-service
image: order-service:v2

# 順序:
# 1. 新しいPodを1つ起動
# 2. ヘルスチェック通過したら既存のPodを1つ終了
# 3. 繰り返し(5つすべて置き換えるまで)

# ========== 4. Docker Compose デプロイ ==========
# docker-compose.yml

version: '3.8'

services:
order-service:
image: order-service:latest
deploy:
replicas: 3
update_config:
parallelism: 1 # 一度に1つずつ
delay: 10s # 10秒間隔
failure_action: rollback # 失敗時にロールバック
restart_policy:
condition: on-failure

# デプロイ
docker stack deploy -c docker-compose.yml myapp

# ========== 5. CI/CD パイプライン ==========
# .github/workflows/deploy.yml

name: Deploy Microservices

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
# 変更されたサービスを検出
- name: 変更されたサービスを検出
uses: dorny/paths-filter@v2
id: changes
with:
filters: |
user-service:
- 'services/user/**'
product-service:
- 'services/product/**'

- name: ユーザーサービスをデプロイ
if: steps.changes.outputs.user-service == 'true'
run: |
docker build -t user-service:${{ github.sha }} services/user
docker push user-service:${{ github.sha }}
kubectl set image deployment/user-service user-service=user-service:${{ github.sha }}

- name: 商品サービスをデプロイ
if: steps.changes.outputs.product-service == 'true'
run: |
docker build -t product-service:${{ github.sha }} services/product
docker push product-service:${{ github.sha }}
kubectl set image deployment/product-service product-service=product-service:${{ github.sha }}

Q5. モノリシックからマイクロサービスへ移行するには?

A:

// ========== 段階的な移行戦略 ==========

// ========== ステップ1: Strangler Fig パターン ==========
// 新機能はマイクロサービスで、既存機能は維持

┌─────────────────────────────────┐
│ モノリシックアプリケーション │
│ ┌──────────┬──────────┬──────┐ │
│ │ユーザー管理│商品管理 │注文 │ │
│ │ │ │管理 │ │
│ └──────────┴──────────┴──────┘ │
└─────────────────────────────────┘

// ステップ1: 新機能(通知)をマイクロサービスで
┌─────────────────────┐ ┌──────────────┐
│ モノリシック │ │通知サービス │
│ ユーザー│商品│注文 │ (新規作成)
└─────────────────────┘ └──────────────┘

// ステップ2: 注文機能を分離
┌─────────────────────┐ ┌──────────────┐
│ モノリシック │ │注文サービス │
│ ユーザー│商品 │ (分離)
└─────────────────────┘ └──────────────┘
┌──────────────┐
│通知サービス │
└──────────────┘

// ステップ3: すべての機能を分離
┌──────────┐ ┌──────────┐ ┌──────────┐
│ユーザー │ │商品 │ │注文 │
│サービス │ │サービス │ │サービス │
└──────────┘ └──────────┘ └──────────┘
┌──────────┐
│通知サービス│
└──────────┘

// ========== ステップ2: API Gateway の導入 ==========

// 以前: クライアントがモノリシックを直接呼び出し
const response = await fetch('http://monolith/api/orders');

// 変更後: API Gateway を経由して呼び出し
const response = await fetch('http://api-gateway/api/orders');

// API Gateway がルーティング
if (route === '/api/orders') {
// 新しいサービスへルーティング
proxy('http://order-service/orders');
} else {
// まだモノリシックへ
proxy('http://monolith/api');
}

// ========== ステップ3: データベースの分離 ==========

// 問題: 共有データベース
┌────────────────┐
│ モノリシックDB
│ ┌──────────┐ │
│ │Users │ │
│ │Products │ │
│ │Orders │ │
│ └──────────┘ │
└────────────────┘

// 解決策: Database per Service

// 1) デュアルライト (Dual Write)
async function createOrder(data) {
// モノリシックDBにも書き込み
await monolithDB.orders.create(data);

// 新しいサービスのDBにも書き込み
await orderServiceDB.orders.create(data);
}

// 2) Change Data Capture (CDC)
// モノリシックDBの変更を自動的に同期
const debezium = require('debezium');

debezium.on('orders.insert', async (change) => {
// 新しいサービスのDBに反映
await orderServiceDB.orders.create(change.data);
});

// 3) 完全な分離
┌──────────┐ ┌──────────┐ ┌──────────┐
│Users DB │ │Products │ │Orders DB
(MongoDB) │ │DB(MySQL)(Postgres)
└──────────┘ └──────────┘ └──────────┘

// ========== ステップ4: 段階的なトラフィック切り替え ==========

// API Gateway で比率を調整
const MIGRATION_PERCENTAGE = 10; // 10%のみ新しいサービスへ

app.use('/api/orders', (req, res, next) => {
if (Math.random() * 100 < MIGRATION_PERCENTAGE) {
// 新しいサービスへ
proxy('http://order-service/orders')(req, res, next);
} else {
// モノリシックへ
proxy('http://monolith/api/orders')(req, res, next);
}
});

// 徐々に増加
// 10% → 25% → 50% → 75% → 100%

// ========== ステップ5: 監視とロールバック準備 ==========

const NEW_SERVICE_ERROR_THRESHOLD = 0.05; // 5%のエラー率

async function monitorNewService() {
const errorRate = await getErrorRate('order-service');

if (errorRate > NEW_SERVICE_ERROR_THRESHOLD) {
// エラー率が高ければロールバック
console.error('エラー率が高い!モノリシックにロールバック');
MIGRATION_PERCENTAGE = 0;

// アラート
await sendAlert('移行のロールバックが発生しました');
}
}

// ========== 実践チェックリスト ==========

移行チェックリスト:

1. 境界の識別
- ドメイン駆動設計 (DDD)
- ビジネス機能別に分割

2. 最も独立した機能から開始
- 依存性が少ないもの
- ビジネスへの影響が少ないもの
-: 通知、ロギング、検索

3. API Gateway の導入
- 段階的なトラフィック切り替え

4. データベース分離戦略
- デュアルライト → CDC → 完全な分離

5. 監視の強化
- エラー率、応答時間、トラフィック
- ロールバックの準備

6. チームのトレーニング
- マイクロサービスアーキテクチャ
- DevOps ツール (Docker, Kubernetes)

7. ドキュメント化
- サービスカタログ
- API ドキュメント
- デプロイガイド

🎓 次のステップ

マイクロサービスアーキテクチャを理解したら、次を学びましょう:

  1. Dockerとは? (ドキュメント作成予定) - コンテナ化
  2. CI/CDとは? - 自動化デプロイ
  3. REST API vs GraphQL - API 設計

実践してみる

# ========== 1. 簡単なマイクロサービス実践 ==========

# プロジェクト構造
mkdir microservices-demo
cd microservices-demo

# サービス作成
mkdir -p services/{user,product,order}
mkdir gateway

# Docker Compose 作成
cat > docker-compose.yml

# 実行
docker-compose up -d

# ログ確認
docker-compose logs -f

# ========== 2. Kubernetes デプロイ ==========

# minikube インストール (ローカル K8s)
brew install minikube
minikube start

# デプロイ
kubectl apply -f kubernetes/

# サービス確認
kubectl get pods
kubectl get services

# ログ確認
kubectl logs <pod-name>

# ========== 3. Istio インストール (Service Mesh) ==========

# Istio インストール
istioctl install --set profile=demo -y

# サービスにサイドカーを注入
kubectl label namespace default istio-injection=enabled

# Istio ダッシュボード
istioctl dashboard kiali

🎬 まとめ

マイクロサービスアーキテクチャは拡張性と柔軟性を提供します:

  • 独立性: サービスごとに独立した開発、デプロイ、拡張
  • 技術の多様性: サービスごとに最適な技術を選択
  • 障害の分離: 1つのサービスの障害が全体に影響しない
  • チームの自律性: チームごとにサービスを責任

ただし複雑度が増加するため、プロジェクト規模とチームの能力を考慮して選択してください! 🧩