フレームワーク

同じコンセプト、違うルール ―― B2B SaaS の共通化を、どの『高さ』で取るか

同じ機能、違うルール

ある 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 つ目の自治体の固有事情で破綻する。先に各自治体で個別に走らせて、共通点を抽出していく順序の方が、結果として早い。

「先に分けて、後で共通化を取る」 ―― これを地域に翻訳した話を、いつか書きたい。

フレームワーク の一覧へ