複数ショップを同じコードで動かす時、

最初は設定ファイルを増やせばいけると思っていました。

ショップ名、ロゴ、テーマカラー、商品取得先、メニュー。

それぞれを tenant ごとの config にしておけば良さそう。

ただ、少し進めると、

「設定で吸収するもの」と「コードを分けるもの」の境界が気になってきました。

なんでも config にすると読みにくいし、

なんでも component 分岐にすると増えた時に怖い。

最初に置いた形

ざっくりこういう設定を考えました。

interface TenantConfig {
  id: string;
  name: string;
  domains: string[];
  theme: {
    primaryColor: string;
    logoUrl: string;
  };
  commerce: {
    shopDomain: string;
    accessToken: string;
  };
  navigation: {
    mainMenuHandle: string;
  };
}

このくらいなら良さげです。

ショップごとに違うけど、アプリの構造は変わらないもの。

ここは設定で持っておくと楽。

だんだん迷うところ

迷ったのは、トップページやセクションの違いです。

あるショップだけ特別なヒーローを出したい。

あるショップだけランキングを出したい。

あるショップだけニュースを別の見せ方にしたい。

こういう要望が来た時に、

if (tenant.id === 'shop-a') {
  return <SpecialHome />;
}

とやると早いです。

早いんですが、これを増やしていくと、たぶん後で読むのがつらい。

なので、設定で差し替えられるところは差し替えたい。

interface TenantConfig {
  id: string;
  components?: {
    home?: 'standard' | 'magazine' | 'campaign';
  };
}

でも、これもやりすぎると config が小さいCMSみたいになっていきます。

ああ、これは危ないなと。。。

config に向いているもの

触っていて、config に向いているのはこのあたりだと思いました。

  • ドメイン
  • API接続情報
  • テーマカラー
  • ロゴ
  • メニューの取得キー
  • feature flag
  • 表示する/しない程度の切り替え

値で表せるもの。

あと、増えても読みやすいもの。

code に残した方が良さそうなもの

逆に、無理に config にしない方が良さそうなのはこのあたりです。

  • 複雑なレイアウト差分
  • 商品の並び替えロジック
  • 特定ショップだけの導線
  • APIレスポンスの解釈
  • UIの状態管理

この辺りを JSON や TS object に押し込むと、

型はあるのに何をしているか分からない、みたいな状態になります。

それなら component として分けた方がまだ読める。

const homeComponents = {
  standard: StandardHome,
  magazine: MagazineHome,
  campaign: CampaignHome
};

const Home = homeComponents[tenant.components?.home ?? 'standard'];

このくらいなら許せそう。

でも sections: [...] で何でも組めるようにするところまで行くと、

それはもう別の管理画面が必要な気がします。

tenant config が増える問題

テナントが少ないうちは、静的 import でも困りません。

import { shopA } from './shop-a';
import { shopB } from './shop-b';

export const tenants = {
  [shopA.id]: shopA,
  [shopB.id]: shopB
};

ただ、これが増えてくると、

「新しいショップを追加する時に、どこを触ればいいんだっけ?」となりがちです。

domain mapping に追加する。

tenant config を追加する。

テストデータを追加する。

E2E の対象にも入れる。

この手順が README にしかないと、たぶん漏れます。

チェックしたいこと

今なら、tenant config は書くだけじゃなくて、チェックも用意したいです。

for (const tenant of Object.values(tenants)) {
  assert(tenant.id);
  assert(tenant.domains.length > 0);
  assert(tenant.commerce.shopDomain);
  assert(tenant.navigation.mainMenuHandle);
}

本当は zod でも valibot でも JSON Schema でも何でもいいんですが、

「全テナントが最低限の設定を持っている」ことはテストしたい。

人間が見ているだけだと、増えた時に漏れそう。

おわり

マルチテナントの config は便利です。

でも、便利なので何でも入れたくなります。

そこが危ない。

2020年の自分なら、たぶん動いたところで満足していたと思います。

今はもう少しだけ先を見て、

「この設定が10個、20個に増えても読めるか」

「新しいショップを追加する時に、漏れを検知できるか」

を気にするようになりました。

全部を完璧に設計する必要はないけど、

設定の境界とチェックだけは、早めに置いておくと後で効きそうです。