ストーリー
佐
佐藤CTO
Step 3ではアーキテクチャスタイルの選択肢を比較検討していく
佐
佐藤CTO
最初のテーマは”モノリスとマイクロサービス”だ。業界では”マイクロサービスにしなきゃ”という風潮があるが、それは本当か?正解は”場合による”だ
佐
佐藤CTO
Netflixがマイクロサービスで成功した話は有名だが、彼らが最初からマイクロサービスだったわけではない。モノリスから始めて、必要に迫られて移行した。この”なぜ移行が必要だったのか”を理解することが重要だ
モノリシックアーキテクチャ
モノリスとは
モノリスとは、アプリケーション全体が単一のデプロイメント単位として構成されるアーキテクチャスタイルです。全ての機能が1つのプロセスで動作します。
graph TD
subgraph Mono["モノリシックアプリケーション"]
U["ユーザー管理<br/>Service<br/>Repository"]
O["注文管理<br/>Service<br/>Repository"]
P["決済処理<br/>Service<br/>Repository"]
DB["共有データベース"]
U --> DB
O --> DB
P --> DB
end
classDef moduleStyle fill:#4a90d9,stroke:#2c5f8a,color:#fff
classDef dbStyle fill:#e8a838,stroke:#b07c1e,color:#fff
class U,O,P moduleStyle
class DB dbStyle
モノリスの利点
| 利点 | 説明 |
|---|
| 開発の単純さ | 1つのコードベースで全体を把握できる |
| デプロイの容易さ | 1つのアーティファクトをデプロイするだけ |
| テストの容易さ | End-to-Endテストが簡単に実行できる |
| デバッグの容易さ | スタックトレースが1プロセス内で完結 |
| トランザクションの容易さ | ACIDトランザクションがそのまま使える |
| チーム立ち上げの速さ | 分散システムの知識が不要 |
モノリスの欠点
| 欠点 | 説明 |
|---|
| スケーリングの制限 | 特定の機能だけをスケールできない |
| デプロイリスク | 小さな変更でも全体の再デプロイが必要 |
| 技術選択の制約 | 全体で同一の言語・フレームワークを使用 |
| コードの肥大化 | 成長とともにビルド・起動時間が増大 |
| チーム間の干渉 | 変更が他チームの機能に影響する可能性 |
| 障害の伝播 | 1箇所の障害が全体に波及する |
モノリスが適しているケース
// モノリスが適切な判断基準
const shouldUseMonolith = (context: ProjectContext): boolean => {
return (
context.teamSize <= 10 && // 小規模チーム
context.domainComplexity === 'LOW_TO_MEDIUM' && // ドメインが比較的単純
context.scalingRequirements === 'UNIFORM' && // 均一なスケーリングで十分
context.deploymentFrequency <= 'WEEKLY' && // 週次以下のデプロイ頻度
context.timeToMarket === 'CRITICAL' // 早期リリースが最優先
);
};
マイクロサービスアーキテクチャ
マイクロサービスとは
マイクロサービスとは、アプリケーションをビジネス機能ごとに独立したサービス群として構成するアーキテクチャスタイルです。各サービスは独立してデプロイ・スケール可能です。
graph TD
subgraph US["ユーザーサービス"]
UA["API"] --> UD["Domain"] --> UDB["DB<br/>PostgreSQL"]
end
subgraph OS["注文サービス"]
OA["API"] --> OD["Domain"] --> ODB["DB<br/>MongoDB"]
end
subgraph PS["決済サービス"]
PA["API"] --> PD["Domain"] --> PDB["DB<br/>PostgreSQL"]
end
classDef apiStyle fill:#67b7dc,stroke:#3a8ab5,color:#fff
classDef domainStyle fill:#4a90d9,stroke:#2c5f8a,color:#fff
classDef dbStyle fill:#e8a838,stroke:#b07c1e,color:#fff
class UA,OA,PA apiStyle
class UD,OD,PD domainStyle
class UDB,ODB,PDB dbStyle
マイクロサービスの利点
| 利点 | 説明 |
|---|
| 独立デプロイ | サービスごとに個別のデプロイサイクル |
| 独立スケーリング | 負荷の高いサービスだけスケール可能 |
| 技術の多様性 | サービスごとに最適な技術を選択 |
| 障害の隔離 | 1サービスの障害が他に波及しにくい |
| チームの自律性 | 各チームが独立して意思決定・開発 |
| コードの理解容易性 | 個々のサービスは小さく理解しやすい |
マイクロサービスの欠点
| 欠点 | 説明 |
|---|
| 分散システムの複雑さ | ネットワーク障害、遅延、データ整合性 |
| 運用コスト | 監視、ログ集約、デプロイパイプラインが複雑化 |
| テストの困難さ | サービス間の統合テストが複雑 |
| データ整合性 | 分散トランザクションの管理が必要 |
| サービス間通信 | レイテンシの増加、通信障害への対策 |
| デバッグの困難さ | 分散トレーシングが必須 |
マイクロサービスが適しているケース
// マイクロサービスが適切な判断基準
const shouldUseMicroservices = (context: ProjectContext): boolean => {
return (
context.teamSize > 20 && // 複数チームが必要な規模
context.domainComplexity === 'HIGH' && // 境界が明確な複雑ドメイン
context.scalingRequirements === 'SELECTIVE' && // 部分的なスケーリングが必要
context.deploymentFrequency >= 'DAILY' && // 高頻度デプロイ
context.organizationalMaturity === 'HIGH' // DevOps・SREの成熟度が高い
);
};
モジュラーモノリス:中間地点
モジュラーモノリスとは
モジュラーモノリスは、単一のデプロイメント単位を維持しながら、内部をモジュール単位に分割するアーキテクチャです。モノリスの利点を保ちつつ、マイクロサービスへの移行を容易にします。
graph TD
subgraph MM["モジュラーモノリス"]
subgraph UM["User Module"]
UAPI["Public API"]
UDomain["Internal Domain"]
UData["Data Access"]
UAPI --> UDomain --> UData
end
subgraph OM["Order Module"]
OAPI["Public API"]
ODomain["Internal Domain"]
OData["Data Access"]
OAPI --> ODomain --> OData
end
UAPI <-->|"Public API"| OAPI
DB["共有DB(スキーマは分離)"]
UData --> DB
OData --> DB
end
classDef apiStyle fill:#67b7dc,stroke:#3a8ab5,color:#fff
classDef domainStyle fill:#4a90d9,stroke:#2c5f8a,color:#fff
classDef dataStyle fill:#5cb85c,stroke:#3d8b3d,color:#fff
classDef dbStyle fill:#e8a838,stroke:#b07c1e,color:#fff
class UAPI,OAPI apiStyle
class UDomain,ODomain domainStyle
class UData,OData dataStyle
class DB dbStyle
TypeScriptでのモジュール分離
// modules/user/public-api.ts
// 各モジュールは明示的なPublic APIを通じてのみアクセス可能
export interface UserModule {
findUserById(id: string): Promise<UserDto | null>;
createUser(command: CreateUserCommand): Promise<string>;
}
// modules/user/internal/UserModuleImpl.ts
class UserModuleImpl implements UserModule {
constructor(private userRepo: UserRepository) {}
async findUserById(id: string): Promise<UserDto | null> {
const user = await this.userRepo.findById(id);
return user ? this.toDto(user) : null;
}
async createUser(command: CreateUserCommand): Promise<string> {
const user = User.create(command.name, Email.of(command.email));
await this.userRepo.save(user);
return user.id.value;
}
private toDto(user: User): UserDto {
return { id: user.id.value, name: user.name, email: user.email.value };
}
}
// modules/order/internal/OrderService.ts
// OrderモジュールはUserモジュールのPublic APIのみに依存
class OrderService {
constructor(
private orderRepo: OrderRepository,
private userModule: UserModule // Public APIを通じてアクセス
) {}
async createOrder(userId: string, items: OrderItem[]): Promise<string> {
// 直接UserRepositoryにアクセスしない
const user = await this.userModule.findUserById(userId);
if (!user) throw new Error('ユーザーが見つかりません');
const order = Order.create(userId, items);
await this.orderRepo.save(order);
return order.id.value;
}
}
モジュラーモノリスの利点
| 利点 | 説明 |
|---|
| モジュール境界の明確化 | マイクロサービス移行時の分割単位が明確 |
| デプロイの単純さ | モノリスと同じく1つのデプロイ単位 |
| データ整合性 | ACID トランザクションが使える |
| 段階的な移行 | 準備ができたモジュールから順にサービス化 |
| 低い運用コスト | 分散システムのインフラが不要 |
移行パターン:Strangler Fig
Strangler Figとは
Strangler Fig(絞め殺しの木)パターンは、既存のモノリスを段階的に新しいアーキテクチャに置き換える移行戦略です。名前はイチジクの木が宿主の木を徐々に覆い尽くす様子に由来します。
graph TD
subgraph Phase1["Phase 1: ルーティング層を追加"]
GW1["API Gateway / Router"]
GW1 --> NEW1["新サービス
(注文)"]
GW1 --> OLD1["旧モノリス
(全機能)"]
end
subgraph Phase2["Phase 2: 段階的に機能を移行"]
GW2["API Gateway"]
GW2 --> O2["注文"]
GW2 --> P2["決済"]
GW2 --> I2["在庫"]
GW2 --> OLD2["旧モノリス
(残機能)"]
end
subgraph Phase3["Phase 3: 完了"]
GW3["API Gateway"]
GW3 --> O3["注文"]
GW3 --> P3["決済"]
GW3 --> I3["在庫"]
GW3 --> U3["ユーザー"]
end
Phase1 ~~~ Phase2 ~~~ Phase3
classDef gwStyle fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#92400e
classDef newStyle fill:#d1fae5,stroke:#059669,color:#065f46
classDef oldStyle fill:#fee2e2,stroke:#dc2626,color:#991b1b
classDef svcStyle fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
class GW1,GW2,GW3 gwStyle
class NEW1,O2,P2,I2,O3,P3,I3,U3 newStyle
class OLD1,OLD2 oldStyle
実装例:API Gatewayによるルーティング
// API Gateway(Express例)
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
const app = express();
// Phase 1: 注文サービスだけ新サービスにルーティング
app.use('/api/orders', createProxyMiddleware({
target: 'http://order-service:3001',
changeOrigin: true,
}));
// その他はまだモノリスにルーティング
app.use('/api', createProxyMiddleware({
target: 'http://legacy-monolith:3000',
changeOrigin: true,
}));
// Phase 2: 決済サービスも移行完了
app.use('/api/payments', createProxyMiddleware({
target: 'http://payment-service:3002',
changeOrigin: true,
}));
// Feature flagによる段階的切り替え
app.use('/api/inventory', (req, res, next) => {
const useNewService = featureFlags.isEnabled('new-inventory-service', {
userId: req.headers['x-user-id'],
});
if (useNewService) {
return createProxyMiddleware({
target: 'http://inventory-service:3003',
})(req, res, next);
}
return createProxyMiddleware({
target: 'http://legacy-monolith:3000',
})(req, res, next);
});
実際の移行事例
Netflix
| 段階 | 内容 |
|---|
| 初期(2007年以前) | モノリシックなデータセンターアプリケーション |
| きっかけ(2008年) | データベース障害で3日間サービス停止 |
| 移行開始(2009年) | AWSへの移行と同時にマイクロサービス化開始 |
| 移行完了(2016年) | 約700以上のマイクロサービスで運用 |
| 教訓 | 「マイクロサービスが正解ではなく、Netflixの規模・課題に対して必要だった」 |
Amazon
| 段階 | 内容 |
|---|
| 初期(2001年以前) | 巨大なモノリス(ビルドに数時間) |
| きっかけ | 開発速度の著しい低下、チーム間の依存関係 |
| アプローチ | 「Two-Pizza Team」ルール、サービス指向アーキテクチャ |
| 成果 | 各チームが独立してデプロイ、開発速度が大幅に向上 |
| 教訓 | 「組織構造とアーキテクチャは密接に関連する(コンウェイの法則)」 |
Shopify(モジュラーモノリスの成功例)
| 段階 | 内容 |
|---|
| 選択 | マイクロサービスではなくモジュラーモノリスを選択 |
| 理由 | 組織の規模と運用能力に対してマイクロサービスは過剰 |
| 実装 | Railsモノリス内でのコンポーネント分離 |
| 成果 | 開発速度を維持しつつ、モジュール境界を明確化 |
| 教訓 | 「マイクロサービスへの移行は必要になるまで待つべき」 |
判断基準の比較表
| 判断基準 | モノリス | モジュラーモノリス | マイクロサービス |
|---|
| チーム規模 | 1-10人 | 10-30人 | 30人以上 |
| ドメインの複雑さ | 低〜中 | 中〜高 | 高 |
| デプロイ頻度 | 週次以下 | 週次〜日次 | 日次〜1日複数回 |
| スケーリング要件 | 均一 | 部分的 | 高度に部分的 |
| 運用成熟度 | 低 | 中 | 高(DevOps/SRE必須) |
| 初期コスト | 低 | 低〜中 | 高 |
| 長期保守性 | 低下しやすい | 良好 | 良好 |
| データ整合性 | 容易(ACID) | 容易(ACID) | 困難(結果整合性) |
| 障害影響範囲 | 全体 | 全体 | サービス単位 |
意思決定フローチャート
チーム規模 > 20人?
/ \
No Yes
/ \
モノリスで開始 ドメイン境界が明確?
将来の拡張性が心配? / \
/ \ No Yes
No Yes / \
/ \ モジュラーモノリス DevOps成熟度高い?
モノリス モジュラー で境界を探る / \
モノリス No Yes
/ \
モジュラーモノリス マイクロサービス
でまず始める
まとめ
| ポイント | 内容 |
|---|
| モノリス | 小規模チーム・初期段階で最適、シンプルだが拡張に限界 |
| マイクロサービス | 大規模・高成熟度の組織向け、独立性は高いが複雑 |
| モジュラーモノリス | 両者の中間、段階的移行の出発点として優秀 |
| Strangler Fig | 既存システムを段階的に置き換える移行戦略 |
| コンウェイの法則 | 組織構造がアーキテクチャを決定する |
| 重要な原則 | 「必要になるまでマイクロサービスにしない」 |
チェックリスト
次のステップへ
次は「イベント駆動とリクエスト応答」を学びます。サービス間の通信パターンを理解し、どのような場面でイベント駆動を選択すべきかを判断できるようになりましょう。
推定読了時間: 30分