ストーリー
ベンダーロックインとは
// ロックインの種類
enum LockInType {
TECHNICAL = "技術的ロックイン", // プロプライエタリAPI・フォーマット
DATA = "データロックイン", // データのエクスポートが困難
CONTRACTUAL = "契約的ロックイン", // 長期契約・最低利用量
SKILL = "スキルロックイン", // 特定技術の専門家に依存
PLATFORM = "プラットフォームロックイン", // エコシステム全体への依存
}
interface LockInAssessment {
service: string;
provider: string;
lockInTypes: LockInType[];
switchingCost: "low" | "medium" | "high" | "very_high";
alternatives: string[];
mitigationStrategy: string;
}
// 評価例
const assessments: LockInAssessment[] = [
{
service: "AWS Lambda",
provider: "AWS",
lockInTypes: [LockInType.TECHNICAL, LockInType.PLATFORM],
switchingCost: "medium",
alternatives: ["Google Cloud Functions", "Azure Functions", "Cloudflare Workers"],
mitigationStrategy: "ビジネスロジックをハンドラから分離し、ポータブルに保つ",
},
{
service: "AWS DynamoDB",
provider: "AWS",
lockInTypes: [LockInType.TECHNICAL, LockInType.DATA],
switchingCost: "very_high",
alternatives: ["MongoDB", "Cassandra", "ScyllaDB"],
mitigationStrategy: "Repository パターンでデータアクセス層を抽象化",
},
{
service: "Firebase Authentication",
provider: "Google",
lockInTypes: [LockInType.TECHNICAL, LockInType.SKILL],
switchingCost: "high",
alternatives: ["Auth0", "Keycloak", "Supabase Auth"],
mitigationStrategy: "認証インターフェースを抽象化し、アダプタで切り替え可能に",
},
];
ロックイン回避の設計パターン
Ports & Adapters(Hexagonal Architecture)
// Port: インターフェースを定義
interface StoragePort {
upload(key: string, data: Buffer): Promise<string>;
download(key: string): Promise<Buffer>;
delete(key: string): Promise<void>;
listObjects(prefix: string): Promise<string[]>;
}
// Adapter: AWS S3 用の実装
class S3StorageAdapter implements StoragePort {
constructor(private s3Client: S3Client) {}
async upload(key: string, data: Buffer): Promise<string> {
await this.s3Client.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key,
Body: data,
}));
return `s3://${process.env.S3_BUCKET}/${key}`;
}
async download(key: string): Promise<Buffer> {
const response = await this.s3Client.send(new GetObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key,
}));
return Buffer.from(await response.Body!.transformToByteArray());
}
async delete(key: string): Promise<void> { /* ... */ }
async listObjects(prefix: string): Promise<string[]> { /* ... */ }
}
// Adapter: GCS 用の実装(将来の移行時に追加)
class GCSStorageAdapter implements StoragePort {
constructor(private storage: Storage) {}
async upload(key: string, data: Buffer): Promise<string> { /* ... */ }
async download(key: string): Promise<Buffer> { /* ... */ }
async delete(key: string): Promise<void> { /* ... */ }
async listObjects(prefix: string): Promise<string[]> { /* ... */ }
}
// アプリケーション層はPortのみに依存
class FileUploadUseCase {
constructor(private storage: StoragePort) {}
async execute(fileName: string, data: Buffer): Promise<string> {
const key = `uploads/${Date.now()}_${fileName}`;
return this.storage.upload(key, data);
}
}
ロックイン受容の判断基準
完全なロックイン回避は非現実的です。受容すべき場合もあります。
## ロックインを受け入れてよい場合
1. **コスト対効果が明確に高い**
- 抽象化のコスト > ベンダー固有サービスの生産性向上
2. **移行の可能性が低い**
- 5年以上使い続ける確信がある
- 業界標準のサービス
3. **代替手段がない**
- 特定ベンダーのみが提供する独自機能
4. **スタートアップフェーズ**
- スピードが最優先で、将来の移行コストを許容できる
## ロックインを避けるべき場合
1. **コア・ビジネスロジック**
- ビジネスの核心はポータブルに保つ
2. **データ格納層**
- データの移行は常に最もコストが高い
3. **マルチクラウドの要件がある場合**
4. **ベンダーの将来性に不安がある場合**
| 判断要素 | 受容 | 回避 |
|---|---|---|
| 生産性向上 | 大きい | 小さい |
| 移行コスト | 許容範囲 | 致命的 |
| ベンダーの安定性 | 高い | 不明 |
| 代替手段 | ある | ない |
| ビジネスへの影響 | 限定的 | 致命的 |
ロックイン対策チェックリスト
## 新しいサービス導入時のチェック
- [ ] ロックインの種類を特定したか
- [ ] スイッチングコストを見積もったか
- [ ] 代替手段を調査したか
- [ ] データのエクスポート方法を確認したか
- [ ] 抽象化レイヤーの必要性を判断したか
- [ ] チーム内の合意を得たか
- [ ] 判断理由をADRに記録したか
まとめ
| ポイント | 内容 |
|---|---|
| ベンダーロックイン | 特定ベンダーへの依存で移行困難になること |
| 種類 | 技術的、データ、契約、スキル、プラットフォーム |
| 回避策 | Ports & Adaptersパターンで抽象化層を設ける |
| 受容 | 生産性と移行コストのバランスで判断 |
チェックリスト
- ベンダーロックインの5つの種類を説明できる
- Ports & Adaptersパターンでの回避方法を理解した
- ロックインを受容すべき判断基準を把握した
- 新サービス導入時のチェックリストを確認した
次のステップへ
次は演習です。ここまで学んだ技術選定のフレームワークを使って、実際に技術選定を行いましょう。
推定読了時間: 30分