共通基盤を厚くするほど、個社向けに自由にコードを書けなくなる。SaaS的な共通プラットフォームを整える運用では、規模が大きくなるほど、個社固有の例外を書くたびに「共通機能の制約に合わせる」ための調整コストが膨らんでいきます。
AI時代に実装単価が下がった今、個社ごとにコードを書き起こすほうが、共通基盤に合わせて妥協するより速い場面が増えてきました。SAP S/4HANAのように「ベースを個社別にdeployしてカスタマイズ」する運用と発想は同じで、これをGitHubの2機能 — Template Repository(型の配布)と Reusable Workflow(共通CIだけ集約)— で軽量に実装できます。
3つの実装モデルの整理から入り、GitHub上での具体的な手順とハマりどころまで順に書きます。
完全SaaSがAI時代に重くなる理由
DSL基盤を作る側のコストがそもそも重い
kintone、Salesforce、Zoho。これらは「カスタムフィールド」「カスタムオブジェクト」をユーザー側で定義できる設計です。利用者にとっては便利ですが、提供側から見ると、ユーザーが画面で定義したスキーマを動的に解釈して、UI・API・権限制御を自動生成する仕組みを製品本体に作り込む必要があります。
つまり、DSL(ドメイン特化言語)と、それを解釈して動かすインタープリタを丸ごと製品に持つことになります。ユーザーが定義したフィールドに合わせてDB列を作り、UI を描き、APIを公開し、権限を判定する。この一連の処理基盤がそのまま製品コアの工数になります。利用者側もDSLの学習コストを払うことになり、kintoneの計算式やSalesforceの数式項目を扱える担当者がいないと、せっかくのカスタマイズ機構が活きません。
そしてここまで挙げた3つは、SaaSの中でも相対的に柔軟な部類です。多くのSaaSはそもそも個社カスタマイズ機構を持たず、提供機能を「そのまま使う」前提に作られています。柔軟性を持たせればDSL基盤の重さを抱え、持たせなければ個社対応の自由度が無い。SaaSというカテゴリ全体に共通する構造的な制約です。
共通基盤化はAI個社最適化の柔軟性を奪う
「共通基盤を整えるほどスケールメリットが出る」というのはSaaSの基本前提です。AI時代にはこの前提が一部反転します。共通基盤を厚くするほど、個社向けに「ちょっとした例外」を書きづらくなるからです。
具体的には、規模が大きくなるほど以下の制約が積み上がります。
- 共通機能を増やすほど、個社固有の例外パターンを「規約に合わせる」ためのコストが増える
- 共通基盤のバージョンアップで個社の実装が壊れる事態が起こり、追従コストが発生する
- 個社向けにAIで実装するとき、共通基盤の制約をすべてプロンプトに乗せる必要があり、設計と生成速度が落ちる
共通基盤の旨味(スケールメリット)と、個社最適化の柔軟性(AIで個社別に書き起こす速度)が、規模が大きくなるほど直接ぶつかります。
AIで個社実装の人月が下がった
ここに最近の変化が乗ります。コーディングAIで「個社ごとに1リポジトリ書き起こす」のが現実的なコストに収まる時代になりました。10年前なら1顧客向けにシステムをスクラッチで書くなど考えられませんでしたが、いまは設計と要件さえ固まればコードはAIに任せられる範囲が広いです。
個社ごとに別リポジトリで持っていれば、共通基盤の制約をプロンプトに乗せる必要がなく、AIに「この顧客向けに自由に書いて」と指示できます。共通基盤の追従コストもなく、個社の例外パターンが書きやすい。AIの柔軟さを最も活かせる構造です。
「SaaS is Dead」議論の核心は、UIで完結する単機能SaaSがAIエージェントに置換されることです。一方でバックエンドの業務ロジックは、個社最適化したほうが価値が出る方向に振り直されつつあります。
3つの実装モデルを並べてどこに座るか決める
業務アプリケーションを「個社差異をどう吸収するか」で並べると、実装モデルは大きく3つに整理できます。
| モデル | 例 | 個社差異の扱い | 実装コスト |
|---|---|---|---|
| 完全SaaS(DSL型) | kintone, Zoho, Salesforce | カスタムフィールドDSLでユーザーが定義 | 超重い(製品本体がDSL基盤) |
| AIコア + 個社アダプタ | UiPath, AutomationAnywhere, 多くの業務AI | コアは共通、I/Oと出力アダプタは個社別モジュール | 中(境界設計が肝) |
| 個社別インスタンス | SAP S/4HANA カスタマイズ | フォーク or 個社別deploy、コードは共通 | 中(運用負荷が高い) |
完全SaaS(DSL型)は、規模が出たときに製品コアの初期投資を回収できる構造です。kintoneやSalesforceのような巨人サイズの投資が前提となります。
個社別インスタンス(フォークや個社別deploy)はSAP S/4HANAのカスタマイズ運用が代表例です。コードは共通でも個社ごとに別環境を持つので、運用負荷が高く、アップグレード追従にも個社ごとの工数が必要になります。
中段の「AIコア + 個社アダプタ」は、UiPathやAutomationAnywhereのような業務AI製品で採用されているモデルです。コアロジックは共通、マスタデータの入出力と外部システム連携だけ個社別モジュールにする発想。中段はコア部分が共通モジュールとして固定されるので、AIで個社最適化するときの自由度はその範囲内に制約されます。
下段の「個社別インスタンス」は、SAP S/4HANAカスタマイズが代表例です。コードは共通の出発点を持ちつつ、個社ごとに別環境・別deployで運用するモデル。本記事が狙うのはこの方向で、Template Repository(共通の出発点を配布)+ Reusable Workflow(CI/CDだけ集約) という形でGitHub上に軽量に再現します。中段との違いは、共通化する範囲を最小限(CI/CDだけ)にとどめること。本体コードは個社で自由に書ける状態を保つことで、AIによる個社最適化の柔軟性を確保します。
「Use this template」で配る型を作る
テンプレートリポジトリを有効化する
リポジトリの Settings → 「Template repository」をチェックすれば終わりです。これだけで「Use this template」ボタンが有効になり、ボタンから新規リポジトリが作れます。
フォークとの違いは、コミット履歴を引き継がない点です。新規リポジトリにはテンプレ最新コミットの内容だけがコピーされ、親リポジトリへの寄与関係も持ちません。「個社向けの完全独立リポジトリ」を作るのに向いた挙動です。
何をテンプレに入れて、何を入れないか
テンプレに入れる候補と入れない候補の線引きが、後々の追従コストを大きく左右します。
| 入れる(共通の足場) | 入れない(個社で書き換わる) |
|---|---|
| ディレクトリ構成 | 業務ロジック本体 |
| 共通設定(tsconfig, eslint, .gitignore, .editorconfig) | 環境変数・シークレットの実体 |
.github/workflows/ci.yml(Reusable Workflowを呼ぶだけのファイル) |
マスタデータ・スキーマ定義 |
| README、CLAUDE.md、docs/ の雛形 | 個社固有の認証情報・接続先 |
判断軸は「全顧客で同じになるか」「個社が後から書き換える前提か」のどちらに該当するかです。迷ったら入れないほうが安全です。
ディレクトリ構成例
my-template/
├── .github/
│ └── workflows/
│ └── ci.yml # uses: org/shared-workflows/...
├── src/
│ ├── core/ # 共通ロジック(個社で必要なら編集可)
│ └── adapters/ # 個社差分を入れる場所(READMEで明示)
├── tsconfig.json
├── eslint.config.js
└── README.md
src/adapters/ のように「ここに個社差分を寄せる」という境界をテンプレ自身が宣言しているのがポイントです。新規顧客対応で迷ったときの判断軸になります。
Reusable Workflowで共通CIを一箇所に集める
workflow_callの最小構成
共通CIを置く場所として、org/shared-workflows のような専用リポジトリを1つ用意します。.github/workflows/build.yml に on: workflow_call: を書くだけで、外部リポジトリから呼び出せるワークフローになります。
on:
workflow_call:
inputs:
node-version:
type: string
default: "20"
secrets:
NPM_TOKEN:
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
- run: npm run build
- run: npm test
個社リポジトリ側の呼び出し(実質1ジョブ)
呼び出し側(顧客リポジトリ)のCIファイルは、Reusable Workflowを uses: で指すだけになります。
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
uses: org/shared-workflows/.github/workflows/build.yml@v1
with:
node-version: "22"
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
これで個社リポジトリのCI設定が「呼ぶだけ」に圧縮されます。共通改修は shared-workflows 側にPRを出せば、消費している全リポジトリが次回タグ更新で取り込めます。
バージョニング戦略は必須
Reusable Workflowを uses: で参照するとき、@<ref> でブランチ名・タグ・コミットSHAのいずれかを指定します。ここで何を指すかが、運用の安定性を大きく左右します。
避けたいのは @main のようなブランチ参照です。共通CI側の main が一度壊れた瞬間、参照している全リポジトリのCIが同時に落ちるからです。共通CIは多くの個社リポジトリから参照される性質上、「壊れた状態が一気に伝播する」リスクが構造的にあります。
実用的なのは、@v1 のような moving major tag を作っておく運用です。v1.0.0 → v1.1.0 のような互換ある修正はそのまま追従させ、breaking changeは v2 を切って、消費側が明示的に上げるまで取り込ませない。GitHub公式アクション(actions/checkout など)と同じバージョニング戦略で、消費者の数だけ「壊れた瞬間に同時に落ちる」リスクが減らせます。
Reusable Workflow と Composite Action と Workflow Templates
混同しやすい3機能の使い分けを整理しておきます。
| 機能 | 単位 | 配置場所 | 使いどころ |
|---|---|---|---|
| Reusable Workflow | ジョブ |
.github/workflows/ 直下 |
共通CI/CDロジック全体を集約 |
| Composite Action | ステップ | action.yml |
細かい処理の部品化 |
| Workflow Templates | ファイル雛形 | org .github リポジトリ |
新規ワークフロー作成のスケルトン |
Reusable Workflow は別ジョブとして起動するので、ファイルシステムは呼び出し元と共有されません。「ビルド成果物を渡す」ようなケースは artifact に頼ることになります。step単位で軽量に再利用したいなら Composite Action が向いています。
全部テンプレに入れたくなる病
テンプレを薄く保つのがこの構成のキモです。運用していると「共通っぽい部分はとりあえずテンプレに入れる」誘惑に勝てない瞬間が出てきます。思い出したいのは、テンプレに入れたものは個社側で書き換えにくくなるという非対称性です。
たとえばORMのスキーマ定義はテンプレに入れたくなりますが、各顧客でスキーマが違うはずなので入れるべきではありません。テンプレ側のORMバージョンを上げる修正をしたとき、各顧客リポジトリのスキーマと衝突してマージできない、という事故が起こります。テンプレに含めるべきは「Prismaを使う前提」だけで、スキーマ本体は個社側にあるべきです。
「テンプレ更新の追従が無い」問題
Use this templateで作ったリポジトリは独立リポジトリです。テンプレ側を改修しても、消費側に自動で取り込まれません。手動で取り込むなら git remote add template ... してマージする運用になりますが、個社で書き換えたファイルとの衝突が増えるほどしんどくなります。
実用的な落としどころは 「テンプレを薄く保つ」 です。共通化したいものはReusable Workflow側に寄せて、テンプレには「足場と境界の宣言」だけを残す。テンプレ自体に厚みを持たせると、追従コストが個社数に比例して増えます。
「薄く保つ」がもう一つ効くのは、AI個社最適化の柔軟性を奪わないためです。テンプレが厚いほど、個社で書き換えるたびにテンプレ側の前提をAIにコンテキストとして読ませる必要があり、「この個社向けに自由に書いて」と指示しづらくなります。
secrets: inherit の落とし穴
Reusable Workflowを呼ぶときに secrets: inherit を使うと、呼び出し元の全シークレットが共通CI側に渡ります。便利ですが、共通CIのコードを書ける人なら誰でも各個社のシークレットを参照できる状態になります。信頼境界がぼやけるので、明示渡しに切り替えるのが安全です。
なお secrets: inherit は、同一organizationまたは同一enterprise内のreusable workflow呼び出しでのみ動作します。orgを跨いだ参照では明示渡しが必須です。
jobs:
build:
uses: org/shared-workflows/.github/workflows/build.yml@v1
- secrets: inherit
+ secrets:
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
GitHub公式の2026年Actionsセキュリティロードマップでは、secrets: inherit による暗黙の継承が廃止され、scoped secrets(実行コンテキストに明示的にバインドされたシークレット)への移行が予定されています。明示渡しに寄せておくと、将来的な仕様変更にも耐える書き方になります。
コア・アダプタ・個社の境界を引く
3層を意識する
機能をどこに置くか迷ったら、3層に分けて考えると整理できます。
-
コア(
shared-workflowsリポジトリ): ビルド・テスト・lint・format・デプロイ骨格・セキュリティスキャン -
アダプタ(テンプレートのscaffold): 個社差分が入る場所を型として宣言するだけ(
src/adapters/、config/customer.yamlなど) - 個社(Use this templateで作った個社リポジトリ): マスタI/O、出力アダプタ、業務ルール、環境変数の実体
コアはCIロジック、アダプタは個社が埋める空欄、個社は実装本体、という役割分担です。
共通化判断の問い方
新しい機能をどこに置くか迷ったら、以下を順に問います。
- 全消費者でまったく同じコードで動くか? → コア(Reusable Workflow)
- 形は決まっているが中身は個社で違うか? → アダプタの型をテンプレに入れる
- 形すらも個社次第か? → 個社リポジトリに任せる
迷ったら個社側に置くのが安全です。あとからコアに引き上げるのは安いが、コアから個社にバラすのは高い。コアに引き上げる作業は単純な抽象化ですが、コアから個社へバラす作業は「他の個社では使いたい」「いや使わない」を1社1社に確認する政治コストが乗ります。
受託と自社プロダクトでの適用差
このパターンは受託寄りの運用にハマります。「全顧客に同じCI/CD品質を配る」ニーズが強く、Reusable Workflow側で共通基盤の実績が積み上がるからです。
自社プロダクト + 大型エンタープライズ向け個別チューニングのケースでは、テンプレートを薄く保ちつつ、共通機能はコアSDKをnpm/PyPIパッケージとして配るほうが境界が綺麗になります。CI設定とアプリケーションコードでは、共通化の単位が違うことを意識しておくと迷いが減ります。
まとめ
完全SaaSのDSL基盤は重く、共通基盤化を進めるほどAI時代の個社最適化の柔軟性が削られていきます。GitHub Template Repository + Reusable Workflowは、「共通化はCI/CDだけにとどめ、本体は個社で自由に書く」という構造を軽量に実装する手段です。境界設計(コア・アダプタ・個社の3層)を最初に決めることで、共通化の旨味と個社最適化の柔軟性のバランスが取りやすくなります。
残る課題は、テンプレ自体のバージョン管理 — テンプレ側の更新を個社リポジトリに追従させる仕組み — の設計です。Renovateで .template-version のような目印ファイルを更新通知する案などが考えられますが、ここの自動化はまだ実用パターンが定まっていない領域に見えます。
AIで個社実装の人月が下がった世界では、共通プラットフォームと個社最適化のバランスが再評価されています。GitHubの2機能はそのバランスを、いま手元で組み立てられる形にしてくれる組み合わせです。