同じ機能、違うルール
ある B2B SaaS の設計会議で、2 つの "申込制限機能" の話が並行で出ていた。
- 公的機関向け: 決済前に会場を選択させる / 顔認証を組み合わせる / 6 月 10 日までに実装
- 福祉系事業者向け: 各施設から 1 人のみ参加可能 / 7 月中旬までに実装
どちらも「申込に制限をかける」という同じコンセプトだ。けれど、実際のルールはまったく違う。顔認証 vs 1 施設 1 人。会場選択タイミング vs 施設単位の重複防止。
この種の "業界別ルール差" は、B2B SaaS では日常茶飯事だ。
問題は、これをどう設計するか。
コンセプトもルールも完全に分けて、2 つの機能として独立して作る か、 コンセプトを共通モジュールにして、ルールを設定値として外出しする か。
この判断は、書く時には小さな差に見える。けれど、3 年後のプロダクトの寿命に直接効いてくる。
共通化の "高さ"
僕がこの判断のときに使っているフレームは、共通化の "高さ" という考え方だ。
プロダクトの中で、何かを共通化するときには、共通化を取る "高さ" を決める必要がある。
高 ┌─────────────────────────────────────┐
│ レイヤ 4: コンセプト (Why) │ ← 抽象度高い
│ 申込制限 │
├─────────────────────────────────────┤
│ レイヤ 3: ルール (What) │
│ 1 人 1 申込 / 1 施設 1 人 / 顔認証 │
├─────────────────────────────────────┤
│ レイヤ 2: 設定 (How parameter) │
│ 重複判定キー / タイミング / UI │
├─────────────────────────────────────┤
│ レイヤ 1: 実装 (How code) │ ← 抽象度低い
│ コード上の関数 / DB スキーマ │
低 └─────────────────────────────────────┘
「何を共通化するか」の問いは、「どのレイヤを共通モジュールに置くか」 の問いに置き換えられる。
4 つの選択肢
公的機関向け / 福祉系事業者向け の "申込制限" は、4 つの選択肢で扱える。
選択肢 A: レイヤ 1 で共通化(実装の関数だけ揃える)
実装の関数名を統一する。けれど、ルールも設定もそれぞれ別々。
- 短期メリット: 関数名が揃って、コードレビューが楽
- 長期コスト: ルールごとに別実装が並ぶ。3 年後に 7 業界対応するときに、7 個の実装が並ぶ
選択肢 B: レイヤ 2 で共通化(設定値を外出し)
実装は 1 つ。設定値(重複判定キー、タイミング、UI 表示)を業界別に切り替える。
- 短期メリット: 2 業界対応で、すでに恩恵がある
- 長期コスト: 設定値の組み合わせが指数的に増える可能性。設定の妥当性検証が難しくなる
選択肢 C: レイヤ 3 で共通化(ルールを共通モジュールに)
ルールを共通インタフェースで表現。業界ごとにルールの "実装" を差し替える。
- 短期メリット: ルール拡張に強い
- 長期コスト: 抽象化のコストが先に乗る。最初の 2 業界では恩恵が出にくい
選択肢 D: レイヤ 4 で共通化(コンセプトを共通言語に)
「申込制限機能」というコンセプトだけ共通の語彙にして、実装はそれぞれ独立。
- 短期メリット: ドキュメントや営業資料が共通化できる
- 長期コスト: 内部の実装は分散したまま。技術負債は溜まる
判断基準: 業界差の "持続性" を見る
どの選択肢を取るかは、業界差の "持続性" で決まる、と僕は思っている。
- 業界差が 永続的 (規制や業務慣行に深く根ざしている): 選択肢 A か D
- 業界差が 過渡的 (運用の差にすぎず、いずれ収束する): 選択肢 B
- 業界差が 構造的に同じ で表現だけ違う: 選択肢 C
| 業界差の性質 | 例 | 推奨選択肢 |
|---|---|---|
| 永続的 | 規制・法的要件で違う | A / D |
| 過渡的 | 運用慣行の差 | B |
| 表現だけ違う | 同じ概念を別語で呼ぶ | C |
今回のケースで言うと、
- 公的機関向けの "顔認証付き会場選択" は、その業界の 規制由来 で、永続的に必要
- 福祉系事業者の "施設単位の 1 人制限" は、その業界の 運用慣行由来 で、過渡的
…と読める。
ということは、この 2 つは 同じ "申込制限" のコンセプトに見えるが、業界差の性質が違う ということになる。安易に 1 つの実装で共通化すると、永続的な制約と過渡的な制約が同じ抽象の中で混在する。
僕の判断
このケースで僕がいま選んでいるのは、選択肢 C + D のハイブリッド だ。
- レイヤ 4 (コンセプト): "申込制限" という言葉を、プロダクト内・営業資料・ドキュメントで揃える
- レイヤ 3 (ルール): "ルール" を共通インタフェースで表現する(具体: 「申込可否を判定する関数」をインタフェースとして定義)
- レイヤ 2 (設定): 設定値の外出しはしすぎない(過渡的な業界差まで設定値に持ち込まない)
- レイヤ 1 (実装): 業界ごとに独立、ただし共通インタフェースを満たす
これによって、
- 永続的な業界差は 実装の独立 で吸収
- 過渡的な業界差は 設定の組み合わせ で吸収しない (実装の中で扱う)
- 共通の言語と共通のインタフェースが、プロダクトの "幹" として残る
…という構造になる。
これは、Web プロダクトの世界で言うと "Strategy パターン" に近い設計判断だ。
"サイドアプリ" と "本体" の判断にも、これが効く
ここまでの話は、設計レイヤの話に見えるけれど、実は 「機能をサイドアプリで作るか、本体に組み込むか」 の判断にも同じフレームが使える。
ある会議で、「サイドアプリの機能を本体に組み込むことも検討」という議論が出ていた。これは、
- サイドアプリ = 業界別の "個別実装" を独立で作る
- 本体に組み込む = 共通モジュール化する
…の判断と同じだ。
判断基準は同じ。
- その機能の業界差は永続的か過渡的か
- 業界差の数は、これから増えるのか収束するのか
- 共通化したときの抽象化コストは、何業界目で回収できるのか
サイドアプリで個別実装して 2〜3 業界で動かしたあと、共通点を抽出して本体に組み込む ―― という順序が、結果として一番健全になることが多い。
「先に共通化を取りすぎない」 こと。これが、B2B SaaS の長期的な健康に効く。
共通化を急ぐと、何が起きるか
最後に、共通化を急いだ場合の典型的な悪い結果を 3 つ書いておく。
1. 抽象が、現実に合わなくなる
最初の 2 業界の共通点で抽象を切ると、3 つ目の業界が来たときに抽象が破綻する。結果、全業界の実装を書き直すか、3 つ目だけ別実装を作って分岐を増やすかの 2 択になる。
2. 設定値の組み合わせが爆発する
設定値で業界差を吸収しようとすると、設定値の組み合わせが指数的に増える。「公的機関向け × 関東 × 大規模」みたいな組み合わせが現れ始めると、QA が破綻する。
3. PdM の頭の中で、プロダクトが 1 つに見えなくなる
業界別の特殊ケースが共通モジュールに混ざると、PdM がプロダクトの "幹" を 1 文で言えなくなる。「これは何のプロダクトですか?」に答えにくくなる。これは、長期的にはチームの判断力を奪う。
開いている問い
共通化の "高さ" を決めるのは、PdM の重要な意思決定だ。
僕の経験では、「先に分けて、後で共通化を取る」 が、ほぼ常に正解になる。理由は、業界差の "性質" は、最初の 2 業界では見抜けないことが多いからだ。3 つ目の業界が入ってきて初めて、永続的な差と過渡的な差が区別できるようになる。
これは、Web プロダクトの設計だけでなく、地域プロジェクトの設計にも当てはまる気がしている。
たとえば、複数の自治体に同じ枠組みを展開するとき、最初の 2 自治体で共通化を取りすぎると、3 つ目の自治体の固有事情で破綻する。先に各自治体で個別に走らせて、共通点を抽出していく順序の方が、結果として早い。
「先に分けて、後で共通化を取る」 ―― これを地域に翻訳した話を、いつか書きたい。