導入(問題提起)
「同じ業務システムを複数社(または複数店舗・部門)で安全に使いたい」「受託で作ったWebアプリをパッケージ化(SaaS化)して横展開したい」——中小規模の現場でよく耳にするニーズです。ところが、安易に共有DBや共有ストレージで始めると、テナント間のデータ漏洩、顧客ごとの細かな要件差、運用/請求/サポートのコスト爆発に直面します。本稿では、小さなチームでも実装・運用できる“疑似SaaS”の現実解として、マルチテナント設計の要点を体系化します。キーワードは「Webシステム開発」「業務システム」「Python」「Excel Web化」。
課題の詳細説明
マルチテナントを掲げた瞬間に、単一テナントのときには目立たなかった課題が一気に顕在化します。
- データ分離の不徹底
- 機能差分・設定差分のスプロール
- 請求・課金・見積の煩雑化
- 運用/監査の難易度上昇
- 導入/移行(Excel Web化からの段階移行)
- 誤クエリやバグで他テナントの行が読み出されるリスク - バックアップ/リストア時に一部テナントのみを安全に扱えない
- A社だけの列追加、B社だけの承認フロー、C社だけの色/ロゴ/ドメイン - コード分岐が雪だるま式に増え、テスト範囲が爆発
- 月額/従量/ユーザー数などプラン別の集計ができない
- サポート時に他社データを見ない仕組み、監査ログの粒度、権限の境界が曖昧
- 既存Excelの列揺れ・名寄せ・ID体系不在を抱えたまま複数社分を一括で扱う困難
解決方法
“最小構成で安全にスケール”の原則で、以下の7要点を押さえます。
- 強制テナント束縛(Row-Level Securityの徹底)
- RBACの3ロール標準化
- 設定の外出しとバリアブル設計
- 拡張ポイント(フック/プラグイン)
- データ移行とID体系の標準化(Excel Web化の延長)
- 観点固定のテスト設計
- 請求・課金の集計基盤
- すべての行にtenant_idを付与し、アプリ層・リポジトリ層で強制的に束縛 - ORM(SQLAlchemy等)でscoped_session(tenant_id)を貫通させ、WHEREを自動付与
- tenant_admin / member / auditor(閲覧のみ)を共通定義 - テナント越しの操作は原則不可。運用者は“昇格トークン+監査ログ必須”
- 色/ロゴ/ドメイン/機能フラグは設定テーブル(またはJSON)化 - コード分岐ではなく、テンプレと設定の組み合わせで表現
- 受注確定時・請求締め時など業務イベントにフックを用意 - 追加計算・通知・帳票を“差し込み”で実装、コアを汚さない
- 顧客/商品/担当のコード体系を定義し、名寄せ・重複排除を先に実行 - external_id(旧ID)を保持し、双方向連携の突合に利用
- 各主要機能で“異テナント汚染が無いこと”の自動テストを用意 - 重要APIは200/403/404の権限制御をpytestで網羅
- usage_metricsテーブルで、ユーザー数/実行回数/ストレージを記録 - 月次締めバッチでテナント別集計→請求書PDFをPythonで生成
具体例(設計と実装の断片)
スキーマの最小例(テナント束縛+監査):
-- 主要テーブルは tenant_id を必須にし、複合インデックスを定義
CREATE TABLE reservations (
id INTEGER PRIMARY KEY,
tenant_id INTEGER NOT NULL,
customer_id INTEGER,
date TEXT,
status TEXT CHECK(status IN ('pending','confirmed','canceled')),
created_at TEXT, updated_at TEXT,
created_by TEXT, updated_by TEXT,
UNIQUE(id, tenant_id)
);
CREATE INDEX idx_resv_tenant_date ON reservations(tenant_id, date);
CREATE TABLE audit_logs (
id INTEGER PRIMARY KEY,
tenant_id INTEGER NOT NULL,
actor TEXT, action TEXT, entity TEXT, entity_id TEXT,
before TEXT, after TEXT, acted_at TEXT, ip TEXT
);
Python(Bottle)でのテナント束縛例:
from bottle import Bottle, request, template
import repo, auth
app = Bottle()
def tenant_scope():
# サブドメイン or セッションからtenant_idを解決
return auth.current_tenant_id()
@app.get('/reservations')
def list_reservations():
tid = tenant_scope()
q = request.query.q or ''
rows = repo.find_reservations(tid, q=q)
return template('reservations_list', rows=rows)
運用・サポートの現実解:
- サポート閲覧は“昇格トークン”で一時的に許可し、監査に必ず残す
- 事故時はテナント単位でバックアップ/リストア可能(SQLite→PostgreSQLでも同様の方針)
- ログはテナントIDでパーティションし、ダッシュボードで分離表示
技術的な解説(小規模向けマルチテナントの最小アーキテクチャ)
小さく始めるなら、以下の構成が現実的です。
- 言語/フレームワーク:Python(Bottle/FastAPI)
- DB:SQLiteではじめ、PostgreSQLへ段階移行(SQLAlchemyで抽象化)
- 認証/認可:セッション+RBAC、テナント境界は必ずサーバ側で検証
- ログ/監査:業務ログとアプリログを分離。監査は差分保存
- 設定:テナント設定テーブル+ファイルストレージ(ロゴ等)
Excel Web化からの移行時の要点:
- 既存Excelの列は“正規化しすぎない”範囲でDB化(まずは1シート=1テーブル)
external_idで旧資産と突合、段階的にWebアプリへ誘導- 帳票は当面Excelテンプレ差し込み(openpyxl)→将来PDF化
導入の流れ
2〜6週間を目安に、以下の手順で“安全に横展開”します。
- 現状棚卸(1.5h)
- 最小スコープ定義(0.5d)
- プロトタイプ実装(3〜7d)
- PoC運用(5〜10d)
- 請求・計測の導入(1〜2d)
- 展開と強化(継続)
- 既存機能、Excel資産、顧客ごとの差分、課金方針を把握
- 1機能×2ロール(admin/member)から開始、テナント束縛を先に作る
- CRUD・検索・監査・設定・テーマ差し替えまで
- 2〜3社で並走、差分は“設定/フックで解決できるか”を検証
- usage計測→月次PDF発行を自動化
- RBAC拡張、PostgreSQL移行、API連携、監査の可視化
まとめ
“最初から完璧なSaaS”を目指さず、テナント束縛・RBAC・設定外出し・拡張ポイントの4点を最小で整え、Excel Web化の延長として段階的に強くしていくのが小規模の正攻法です。Pythonを中核に据えたWebシステム開発なら、少人数でも安全に運用できる業務システムのマルチテナント化が実現します。
問い合わせ導線
マルチテナントの要件定義・実装はお問い合わせへ。Excel Web化や業務システムのSaaS化もご相談ください。
Webシステム開発のご相談は monou まで