🎄 科学と神々株式会社 アドベントカレンダー 2025
Hybrid License System Day 3: サービス分割の設計原則
マイクロサービス基礎編 (2/4)
📖 はじめに
Day 3では、サービス分割の設計原則を学びます。ドメイン駆動設計(DDD)の基礎と、Bounded Contextの考え方を理解し、Auth/Admin/Gatewayという分割の設計思想を深く掘り下げます。
🎯 ドメイン駆動設計(DDD)の基礎
DDDとは?
**ドメイン駆動設計(Domain-Driven Design)**は、ビジネスドメインに焦点を当てたソフトウェア設計手法です。
ビジネスドメイン → コードに反映
↓
ドメインモデル(概念モデル)
↓
マイクロサービス境界
Bounded Context(境界づけられたコンテキスト)
Bounded Contextは、特定のドメインモデルが有効な境界を定義します。
┌─────────────────────────────────────────┐
│ License System (全体ドメイン) │
│ │
│ ┌───────────────┐ ┌────────────────┐ │
│ │ Authentication│ │ Administration │ │
│ │ Context │ │ Context │ │
│ │ │ │ │ │
│ │ - User │ │ - Dashboard │ │
│ │ - License │ │ - Statistics │ │
│ │ - Token │ │ - Audit │ │
│ └───────────────┘ └────────────────┘ │
│ │
│ ┌───────────────────────────────────┐ │
│ │ Gateway Context │ │
│ │ - Routing │ │
│ │ - Security │ │
│ │ - Rate Limiting │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
🔍 Hybrid License Systemのサービス分割分析
1. Authentication Context → Auth Service
責務: ユーザー認証とライセンス管理
// Authentication Contextのドメインモデル
class User {
userId: string;
email: string;
passwordHash: string;
plan: string;
}
class License {
licenseId: string;
userId: string;
clientId: string;
status: string;
expiresAt: Date;
}
class Token {
jwt: string;
payload: {
userId: string;
email: string;
plan: string;
clientId: string;
};
}
分離理由:
- セキュリティ: 認証処理は高度なセキュリティが必要
- 独立性: ライセンス検証ロジックは他の機能と独立
- スケーラビリティ: 認証リクエストが多いため独立スケール必要
2. Administration Context → Admin Service
責務: 管理機能と統計情報
// Administration Contextのドメインモデル
class Dashboard {
totalUsers: number;
activeLicenses: number;
revenue: number;
recentActivations: Activation[];
}
class AuditLog {
logId: string;
userId: string;
action: string;
timestamp: Date;
ipAddress: string;
}
class Statistics {
period: string;
userGrowth: number[];
licenseUsage: Map<string, number>;
planDistribution: Map<string, number>;
}
分離理由:
- ユーザー分離: 管理者と一般ユーザーで異なる機能
- データアクセスパターン: 集計・分析クエリが多い
- UI独立性: React SPAとして独立した進化が可能
3. Gateway Context → API Gateway
責務: クライアント向けの統一インターフェース
// Gateway Contextのドメインモデル
class Route {
path: string;
method: string;
targetService: string;
authRequired: boolean;
}
class RateLimit {
ip: string;
userId?: string;
requestCount: number;
windowStart: Date;
}
class SecurityPolicy {
corsOrigins: string[];
rateLimit: number;
jwtSecret: string;
}
分離理由:
- 横断的関心事: CORS、レート制限、認証など
- 単一エントリーポイント: クライアントの簡素化
- バックエンド隔離: 内部構成の変更をクライアントから隠蔽
📐 サービス境界の決め方
原則1: ビジネス能力で分割
各サービスは独立したビジネス価値を提供すべきです。
✅ 良い分割: 「ライセンス認証」サービス
→ ビジネス価値: ライセンスをアクティベートし、検証する
❌ 悪い分割: 「データベースアクセス」サービス
→ 技術的な分割であり、ビジネス価値が不明確
原則2: データの所有権
各サービスは自分のデータを所有すべきです。
Auth Service:
- users テーブル (所有)
- licenses テーブル (所有)
- subscriptions テーブル (所有)
Admin Service:
- audit_logs テーブル (所有)
- statistics キャッシュ (所有)
- ※ users/licenses は参照のみ(共有DB経由)
原則3: 変更の頻度
一緒に変更される機能は同じサービスに配置します。
Auth Service の変更例:
✅ JWT生成ロジック変更 → ライセンス検証ロジックも変更
✅ 新しいプラン追加 → アクティベーション処理も変更
Admin Service の変更例:
✅ ダッシュボードUI変更 → 統計API変更
✅ 新しいレポート追加 → データ集計ロジック変更
原則4: チームの境界
理想的には1チーム = 1サービスです。
Team Authentication:
- Auth Service開発・運用
- セキュリティ専門知識
Team Administration:
- Admin Service開発・運用
- フロントエンド/データ分析
Team Platform:
- API Gateway開発・運用
- インフラ/SRE
🤔 分離すべきか、統合すべきか?
ケーススタディ: User管理
選択肢1: 独立したUser Service
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ User Service│ │ Auth Service │ │Admin Service│
│ │ │ │ │ │
│ - Users │ │ - Licenses │ │ - Dashboard │
│ - Profiles │ │ - Tokens │ │ - Audit │
└─────────────┘ └──────────────┘ └─────────────┘
メリット:
- User管理の独立した進化
- 他サービスからの分離
デメリット:
- サービス間通信の増加
- トランザクション管理の複雑化
選択肢2: Auth Serviceに統合(Hybrid実装の選択)
┌──────────────────┐ ┌─────────────┐
│ Auth Service │ │Admin Service│
│ - Users │ │ - Dashboard │
│ - Licenses │ │ - Audit │
│ - Tokens │ │ - Statistics│
└──────────────────┘ └─────────────┘
メリット:
- トランザクション一貫性
- パフォーマンス(ネットワーク不要)
- シンプルな実装
デメリット:
- Auth Serviceの責務増加
Hybrid実装の判断:
- ユーザーとライセンスは密結合
- トランザクション一貫性が重要
- → Auth Serviceに統合
🔄 Context Mapping(コンテキスト間の関係)
Shared Database Pattern
Hybrid実装ではShared Databaseパターンを採用:
-- 共有データベース(SQLite)
CREATE TABLE users (...); -- Auth Serviceが所有
CREATE TABLE licenses (...); -- Auth Serviceが所有
CREATE TABLE subscriptions (...);-- Auth Serviceが所有
CREATE TABLE audit_logs (...); -- Admin Serviceが所有
関係性:
-
Auth Service → Admin Service: Upstream/Downstream
- Auth Serviceがデータを生成
- Admin Serviceが統計用に参照
-
API Gateway → Auth/Admin: Customer/Supplier
- Gatewayがクライアント要求を転送
- Auth/Adminが実際の処理を提供
Anti-Corruption Layer
Admin ServiceはAnti-Corruption Layerを実装:
// Admin Serviceでの読み取り専用アクセス
class UserRepository {
// Auth Serviceのデータモデルに依存しない
async getUserStatistics() {
// 内部的にusersテーブルを参照
const stmt = db.prepare('SELECT plan, COUNT(*) as count FROM users GROUP BY plan');
const rows = stmt.all();
// Admin Service独自のモデルに変換
return rows.map(row => ({
planName: row.plan,
userCount: row.count
}));
}
}
📊 サービス分割の評価基準
1. 凝集度(Cohesion)
サービス内の機能が密接に関連しているか?
Auth Service:
✅ ライセンスアクティベーション
✅ ライセンス検証
✅ JWT生成
✅ ユーザー認証
→ すべて「認証」という共通のビジネス能力
2. 結合度(Coupling)
サービス間の依存が少ないか?
API Gateway → Auth Service:
✅ HTTP REST API経由の疎結合
✅ サービス間認証(SERVICE_SECRET)
✅ タイムアウト・リトライ設定
Auth Service → Admin Service:
✅ 直接呼び出しなし(データベース共有のみ)
3. 自律性(Autonomy)
サービスが独立してデプロイできるか?
# Auth Serviceのみ更新
docker-compose up -d --build auth-service
# Admin Serviceは影響を受けない
curl http://localhost:3002/health
# → {"status": "healthy"}
🚀 実装における決定事項
Hybrid実装の設計決定
| 決定事項 | 選択 | 理由 |
|---|---|---|
| User管理 | Auth Serviceに統合 | トランザクション一貫性 |
| データベース | Shared Database | 実装の簡素化 |
| サービス間通信 | HTTP REST | 標準的で実装が容易 |
| 認証方式 | JWT | ステートレスでスケーラブル |
| ゲートウェイ | Express.js | 柔軟性とエコシステム |
🎯 次のステップ
Day 4では、サービス間通信パターンについて学びます。同期通信(HTTP/REST)、非同期通信、サービスディスカバリー、タイムアウト・リトライ戦略を実装しましょう。
🔗 関連リンク
次回予告: Day 4では、HTTP RESTによる同期通信と、タイムアウト・エラーハンドリングの実装を詳しく解説します!
Copyright © 2025 Gods & Golem, Inc. All rights reserved.