ADR: organization 将来導入と billing 分離戦略
ステータス: Accepted 決定日: 2026-04-15 関連 Issue: #236, #237, #238, #239
1. 背景
リリース後 1 年前後で「複数ユーザーが所属する organization(チーム)」の概念を導入する可能性がある。現時点では導入は未確定だが、v0.3 マイルストーン「スケーラビリティ・組織移行」に候補として存在する。
リリー ス後に users テーブル中心の所有権構造が固まった後で organization を後付け導入すると、以下のコストが連鎖する。
- 既存テーブルへの
organization_idバックフィル - 認可ロジックの総書き換え(「自分のデータか」→「自分の organization のデータか」)
- Stripe customer の付け替え(user → organization)。既存サブスクの移行は特に重い
- Better Auth の organization plugin を後から有効化する際のスキーマ衝突
本 ADR では、「organization 導入の有無に関わらず単体で正当化できる設計改善」のみを先回りで行い、organization 特化の先行実装は YAGNI として避ける方針を固定する。
2. 方針サマリ
- billing は最初から users と分離する(
billing_accountsテーブル) - API 認可は抽象化する(
canAccess(actor, resource)ヘルパー) - organization 導入時は Better Auth organization plugin を採用する(独自実装しない)
- organization 特化の先行実装は行わない(
organizations/members/owner_typeカラム等は YAGNI)
3. 所有者マトリクス
各リソースについ て「現在の所有者」と「将来 organization 化する可能性」を整理する。
| テーブル | 現在の所有者 | 将来 organization 化 | 備考 |
|---|---|---|---|
users | - | ❌ | 認証主体。organization に属する側 |
billingAccounts (旧 userSubscriptions) | user | ✅ 高 | 課金主体は organization 単位が自然 |
userVideoAccess | user | ✅ 中 | 課金と連動するため organization 側に寄る可能性 |
favoriteChannels | user | ⚠️ 個人 or 共有どちらもあり得る | プロダクト仕様で判断(ユーザーごと or チーム共有) |
searchHistories | user | ❌ 低 | 個人の検索履歴。organization 化しない想定 |
channels / videos | なし(グローバル) | ❌ | 全ユーザー共有のマスターデータ |
4. 決定事項
4.1 billing_accounts 分離(採用)
user_subscriptions を billing_accounts にリネーム・再設計し、Stripe customer を別テーブルで管理する。
理由:
- 「認証ユーザー情報」と「課金顧客情報」は本来別ドメイン。users テーブルが Stripe の都合で膨らむのを防ぐ
- 退会時の扱い: user 削除 ≠ Stripe customer 削除(返金処理・監査ログのため課金レコードは残したい)
- テスト容易性: 課金ロジックだけ独立してモック・テストできる
- 複数決済手段の拡張: 将来 Stripe 以外(App Store 等)が増えた時に
billing_accounts側を拡張すれば済む - organization 導入時: テーブルの所有者を user → organization に付け替えるだけで済む
スキーマ方針:
id(PK),userId(FK, UNIQUE),stripeCustomerId(UNIQUE, nullable),plan,creditBalance,updatedAtowner_typeカラムは追加しない(YAGNI。organization 導入時に ALTER で追加する)
詳細: #238
4.2 canAccess 抽象化(採用)
API 層に canAccess(actor, resource) ヘルパーを導入し、リソース所有者チェックを統一する。
理由:
ctx.user.id === resource.userIdのような直書きがハンドラ・service に散在するのを防ぐ- 認可ロジックを一箇所に集約することでテスト容易性・一貫性が向上する
- 将来
Actor型を{ type: "user" | "organization"; id }に拡張する余地を残せる - organization 導入の有無と無関係に責務分離として有効
設計方針:
Actor型は現状{ type: "user"; id: string }のみ- 将来
| { type: "organization"; id: string; userId: string }を追加する前提で overload を設計
詳細: #239
4.3 Better Auth organization plugin 採用方針(採用)
organization 機能を実装する際は、Better Auth の organization plugin を採用する。独自実装は行わない。
理由:
- plugin が
organizations/members/invitationsテーブルとマイグレーションを提供する - 招待フロー・メンバー管理・権限モデルも標準対応されている
- 独自実装と plugin のスキーマ競合を避けられる
今やること: 特になし。この方針を固定しておくだけで十分(独自 organizations テーブルの先行作成を禁じる意思決定)。
5. 非採用案
5.1 organizations テーブルの先行作成(不採用)
却下理由: Better Auth organization plugin を将来採用する前提に立つと、plugin 提供のスキーマと衝突する。plugin 有効化時に移行が必要になり、先行投資が無駄になる。
5.2 全テーブルへの owner_type カラム先行投入(不採用)
却下理由: organization 導入が未確定のため、使われない可能性がある。未使用カラムは認知コスト・マイグレーションコストを無駄に上げる。必要になった時点で ALTER TABLE で追加すれば済む。