複数プロダクトで 1 つの DB を共有する: テナント分離・履歴・削除・権限を「1 行で付く」ようにする
顧客データをプロダクトごとの DB ではなく単一の集約 DB に置き、テナント分離などの決まりごとを ORM ラッパーで強制する設計。estie の事例。
どんなときに使う?
- 1 社で複数の SaaS を出していて、セキュリティ水準をプロダクトごとにばらつかせたくない
- 同じ顧客データ(ビル、取引先など)を複数プロダクトで使い回したい
- 共通基盤を「専任チームが作るもの」ではなく「各チームがテーブルを足していく置き場」として運用したい
集約 DB で全テーブルに強制すること
| 要件 | 実装 |
|---|---|
| テナント分離 | 全レコードに企業 ID を記録し、トランザクション開始時に認証情報から企業 ID を宣言、DB 設定で一致する行だけ読めるようにする(PostgreSQL) |
| 操作ログ | 閲覧・編集・削除・権限変更のたびにログを出す |
| 変更履歴 | 行をイミュータブルにし、更新は新しいバージョンの insert にする |
| 退会時の一括削除 | 全データの ID を 1 つのメタデータ表で管理し、「ある企業の全データ ID」をすぐ引けるようにする |
| データごとの権限 | 全データ ID に閲覧できる企業・部署・ユーザーを記録し、操作時に確認する |
各チームに意識させない工夫
- メタデータ表: データ ID を全テーブル横断でユニークにすれば、権限や ID 管理は 1 つの表で済む
- API 内部: Rust のマクロで ORM(sea-orm)のラッパーを生成。テーブルごとに
private_data_type!(table_name);を 1 行書くと、find()の代わりにfind_visible_and_latest(org_id, user_id)(権限表の join と、ID ごとの最新版の取得)、insert()の代わりにcreate_object(...)(権限表への書き込み・ID 発行・メタ列の設定・操作ログ)が使える - ミドルウェア: 企業 ID の宣言は 1 回作れば全 API で共通
- 実テーブル定義: ここはまだ仕組み化できず、雛形のコピーとレビューで担保している
権限モデルはあえて単純に
- ファイルシステムのような「親の権限が子に及ぶ」モデルは、ビル・区画・募集・商談のように種類が多く親子関係が木にならないデータでは破綻しやすい
- そこで「データごとに見てよい主体を直接記録する」単純な方式を選び、業務を理解してから作り直せる余地を残した
注意点
- PostgreSQL 17 + Rust + sea-orm での実装例。別スタックなら同じ考え方を ORM のラッパーや Row Level Security で置き換える
- 記事は 2026 年 6 月時点の設計
出典
- @matsu7874 · 2026-07-08
- estie の記事: zenn.dev