product_slug の設計指針

AppCotton では、product_slug が「あなたの製品を一意に識別する主キー」に相当します。
SDK・REST・DB・購入UI・ドメイン認証の全レイヤーで参照されるため、最初の設計が将来の運用コストを大きく左右します。


目的と位置づけ

  • 一意識別子:人間可読 & URL/コードに安全に埋め込める ID(例: combpass_premium
  • 永続性:バージョンや価格改定、販売形態が変わっても変えない(製品ラインを変えるときのみ新規発行)
  • 外部公開:SDK 設定、クライアント側コード、REST クエリ、ショートコードで公開される前提

命名ルール(推奨)

  1. 英小文字 + 数字 + アンダースコアのみ
    • 正規表現: ^[a-z0-9_]+$
  2. 先頭は英字
    • 例: combpass_basic は OK、123_basic は非推奨
  3. 単語は _ で区切る(ハイフンは使わない)
    • 例: appcotton_sdk_pro
  4. 短く・意味がわかる(20〜40 字目安)
    • 例: combpass_premium, foxpot_enterprise
  5. 製品ライン + エディション(/販売系) を基本形に
    • 例: {line}_{edition}combpass_premium
    • エディション例: free, basic, pro, premium, enterprise
  6. 環境や店舗などの局所情報は含めない
    • *_staging, *_jp, *_2025campaign は避ける
    • 期間限定販売は plan で表現する

予約・禁止パターン

  • 予約接頭辞:wp_, appcotton_, system_(内部で使用する可能性)
  • 予約語:default, test, null, true, false, none
  • 禁止文字:スペース、ハイフン、記号、全角文字、絵文字
  • 将来の衝突を避けるため、自プロダクトの接頭辞を付ける(例: cbp_, foxpot_ など)

変更ポリシー

  • 原則、後から変更しない(既発行ライセンス・アクティベーション・Webhook・外部連携が壊れる)
  • 製品の統合/分割やブランド変更が必要な場合は:
    • 新 slug を新規発行 → 旧 slug を「非推奨(deprecated)」マーク → 段階的移行
    • REST で旧→新マッピングを提供(期間限定)

バージョンとエディションの扱い

  • バージョン番号は slug に含めない(例外:長期共存させる別製品として扱う場合のみ)
    • combpass_premium + バージョンは メタ情報/プラン で管理
    • combpass_premium_v2(将来の分岐とデータ移行が複雑化)
  • エディション差は plan(プラン) に寄せる
    • 例:combpass_premium(プロダクト)に 1-site, 3-sites, unlimited 等のプラン

スラグ衝突の回避

  • ユニーク制約を DB レベルで保持(重複登録を弾く)
  • 管理 UI 保存時に重複チェック + サジェスト
    • 例:combpass_premium が存在 → combpass_premium2 を提案しない(悪手)
    • 提案は 意味を維持combpass_enterprise など

短縮形と拡張形のバランス

  • 過度な略称(cp_prem)は避ける
  • 長すぎる説明的名前(combpass_premium_license_server_edition)も避ける
  • 可読性 > 数文字短縮 を優先

国際化(i18n)

  • slug は英数固定(翻訳しない)
  • 表示名・説明は各ロケールで翻訳(name, description フィールド)
  • 複数ブランド展開でも slug は共通にしておくと SDK/REST が単純化

スラグ生成アルゴリズム(参考)

  1. 製品ライン表示名から ASCII に正規化(記号・スペース除去、-_
  2. 英小文字化
  3. 先頭英字でなければ x_ を付加
  4. 禁止語と衝突チェック → サフィックス提案(_plus, _pro など)
function generateSlug(name):
  s = ascii_slugify(name)            // "CombPass Premium" -> "combpass_premium"
  s = s.toLowerCase()
  s = s.replace("-", "_")
  s = s.replaceAll(/[^a-z0-9_]/, "")
  if not s[0].isAlpha(): s = "x_" + s
  if isReserved(s) or existsInDB(s): s = suggestVariant(s) // e.g. "_pro", "_edition"
  return s

運用 FAQ

Q1. プロモや年度キャンペーンでスラグを分けたい?
A. 分けない。プラン価格ルールで表現(期間限定プラン・クーポン・クレジット)

Q2. 無料/有料で別スラグにすべき?
A. 原則 同一スラグ + 別プラン(無料→有料アップグレードを差額課金で一本化)

Q3. 同一エンジンの別製品(例:Lite/Studio)?
A. 機能が大きく分岐し、将来も独立提供なら 別スラグ
片方が上位互換なら 同スラグ + プラン差 を検討。


SDK/REST との対応関係

  • SDK 設定例 AppCotton_SDK::init([ 'endpoint' => 'https://example.com/wp-json/appcotton/v1', 'product_slug' => 'combpass_premium', ]);
  • REST 例
    GET /licenses/validate?license_key=XXXX&item_slug=combpass_premium&domain=https://site.example

→ slug を単純・堅牢にしておくほど、すべての呼び出しが簡潔になります。


データ移行(やむを得ず変更する場合)

  1. 新 slug を作成(旧は保持)
  2. ライセンス・注文・アクティベーションの 移行マップ を内部テーブルで用意
  3. REST/SDK で旧 slug を受けたら 新 slug に透過変換(移行期間のみ)
  4. ドキュメントと UI に移行告知
  5. 期間終了後、旧 slug を 廃止(参照が残るなら read-only リダイレクト)

セキュリティ観点

  • slug は「推測可能」で問題ない(鍵ではない)
  • ただし 列挙耐性のため、REST/閲覧で 内部 ID を出さず slug 固定で参照
  • 監査ログで slug + license_key(マスク) + domain を記録

例:良い/悪い

  • combpass_premium / foxpot_enterprise
  • cbp_pro(自ブランド prefix + edition)
  • Comb-Pass Premium(大文字・ハイフン・スペース)
  • combpass_premium_2025spring(季節要素)
  • combpass_premium_v2(バージョンを含める)

まとめ

  • 変えない前提で設計(寿命は製品ラインと同じ)
  • 短く・意味明確・英数+アンダースコア
  • エディション/価格/期間は plan 側で表現
  • マイグレーション時は新 slug + 透過変換 + 期間明記

これらを守ると、SDK/REST/UI/課金/監査の全体が一貫し、将来の手戻りを最小化できます。