バリアントとバンドルを含む商品カタログ:スキーマとUIパターン
明確なSKUルール、在庫ロジック、UIパターンでバリアントとバンドルを持つ商品カタログを設計し、誤った組み合わせや過剰販売を防ぐ方法。

バリアントとバンドルが急速にややこしくなる理由
単一の商品が1つの価格と1つの在庫数を持つだけならカタログは単純に見えます。色、サイズ、サブスクリプション期間、地域別パッケージなどを追加すると、その単純さは壊れます。単一の「Products」テーブルでは、今売っている「正確なもの」は何か、どう追跡するかといった基本的な問いに答えられなくなります。
購入者は詳細が正しいことを期待します。オプションを選んで即座に正しい価格が表示され、選択した組み合わせが今日発送できるかを知りたいのです。ページに「在庫あり」と表示されていても、選んだサイズがなければ信頼はすぐに落ちます。価格がチェックアウトまで変わらないと、サポート問い合わせや返品が増えます。
バンドルは、見た目は商品でもルールのように振る舞うため、二重の複雑さを加えます。「スターターキット」はボトル1本、ポンプ1つ、フィルター一式を含むかもしれません。可用性は構成パーツに依存し、コストやレポートも一貫して扱う必要があります。
カタログが崩れ始めているサイン:
- オプションを表現するために重複したSKUを作っている。
- バンドル販売後に在庫数が合わないと感じる。
- 管理画面がほぼ同一のアイテムの長いリストになる。
- 単品では機能する割引や税処理がキットで壊れる。
- レポートで「実際に何が売れたか」が答えられない。
直すには規律が必要です:一貫したデータモデルと、顧客やチーム双方に選択や可用性を明示するUIパターンです。
平易な用語の定義:Option、Variant、SKU、Bundle
「バリアント(variants)」という言葉は複数の概念が混ざりがちです。用語を早めに整理すると、後の設計(スキーマ、UI、在庫)がずっと楽になります。
多くのチームが採る定義は次の通りです:
- Option:購買者が選べる選択肢(例:Size、Color)。
- Option value:Optionの中の一つの値(例:Size = M、Color = Black)。
- Variant:Option値の特定の組み合わせ(例:Size M + Color Black)。
- SKU:運用上で追跡する販売可能単位。Variantは多くの場合1つのSKUに対応しますが、常にそうとは限りません。
- Bundle / kit / multipack:他の商品から構成される商品。Bundleはマーケティング的なまとまり(パーツを別売りできることが多い)、kitは「必ず一緒に発送する」セット、multipackは同一アイテムの繰り返し(靴下3点セット)です。
IDも実務で混乱します。内部IDはデータベースの識別子。SKUはピッキングや補充、レポートで使う運用コード。バーコード(UPC/EAN)はスキャナーが読むものです。1つのSKUに複数のバーコードがあることも(地域別など)、バーコードがないアイテムもあります。
「売れるバリアント」にするかどうかの良いルールは:価格、在庫、重量、発送ルールが異なる可能性があるなら売れる単位として扱うこと。例えば同じTシャツでもSize MとSize XLは別々に在庫を持つべきなので、別SKUにするのが普通です。
カタログがサポートすべき要件を決める
スキーマを選ぶ前に、業務が毎日答えなければならない問いを整理しましょう。商品ページで「今すぐ在庫があるか」「いくらか」「どう発送されるか」「どの税ルールが適用されるか」を自信を持って答えられますか?
各「事実」をどこに置くかを決めるのが便利です。共有される事実はProductに、変わりうる事実はVariant(SKU)に置いてください。混ぜると同じ不具合を2箇所で直す羽目になります。
一般的に商品レベルとバリアントレベルを決める基準:
- サイズ・色・素材で変わるならVariantに。
- どの組み合わせでも真ならProductに。
- SKUごとにレポートするもの(売上、マージン、返品)はVariantに保存。
- ピック/パック/発送でオペレーションが使うものは倉庫が扱う場所、つまりSKUに保存。
- 表示名のように安全に派生できるなら、ソースだけ保存して派生は都度生成。
真の単一の情報源を保ってください。例えば「price」をProductとVariantの両方に保存するのは避け、役割を明確にする(ProductはMSRP、Variantは実売価など)場合のみ分けます。
変更に備えましょう。後から新しいOptionを追加したり、Variantを廃止したり、仕入れ先の変更でSKUを統合したりすることがあります。Variantを第一級のレコードとして扱っておくと、この変化がスムーズになります。
AppMasterで構築する場合は、Data Designerでエンティティ命名を明確にすると要件変更時の保守が楽になります。
商品とバリアントのための実用的スキーマ
クリーンなスキーマは、シンプルな商品が多数の販売組み合わせに変化してもカタログを分かりやすくします。目的は、購買者が選ぶ「選択肢」と、実際に在庫し発送する「販売単位」を分けてモデル化することです。
信頼できるエンティティ群:
- Product:親レコード(名前、説明、ブランド、カテゴリー、デフォルト画像)
- Option:選択タイプ(Size、Color)
- OptionValue:許容値(Small、Medium、Red、Blue)
- Variant:販売可能な単位(ある組み合わせの値)
- VariantOptionValue:VariantとOptionValueをつなぐ結合テーブル
Variantの一意性で多くのカタログが間違えます。安全な方法は正規化です:各Variantに対して各Optionにつき1つのOptionValueを強制し、重複組み合わせを防ぎます。速度を重視するなら、color=red|size=m のような計算済みの「variant key」(またはそのハッシュ)をVariantに保存し、Product内で一意にする方法が有効です。
組み合わせごとに変わるフィールドはVariantに置いてください:SKU、バーコード、価格、比較価格、原価、重量、寸法、ステータス(有効/廃止)、画像(メイン画像1枚または小さな画像セット)など。
「素材」や「ケア指示」などのカスタム属性を増やし続けるのは避けましょう。PostgreSQLならProductやVariantのJSONBフィールドに入れて、アプリ側に小さなバリデーション層を置くのが現実的です。
長期的に一貫するSKUルール
SKUは販売した「正確なアイテム」を示す識別子で、安定していることが望まれます。SKUにマーケティング名や全オプション文言、シーズンなどを詰め込みすぎないでください。過剰に意味を持たせると、後で何かを変えるたびにレポートが壊れます。
SKUを手動で割り当てるか自動生成するかを早めに決めましょう。既にERPやバーコード、仕入先SKUと合わせる必要があるなら手動が安全です。多くのバリアントがある場合は生成ルールの方が一貫性を保てますが、ルールが途中で変わると混乱します。中間案としては、管理する固定ベースSKUに対してバリアント属性の短い生成サフィックスを付ける方法がよく使われます。
長持ちするルールの例:
- 注文が入ったらSKUは安定させる。過去のSKUを「直す」ために名前を変更しない。
- 内部IDとSKUを分離する。人にはSKU、DBにはIDを使う。
- 商品ファミリーには短いプレフィックスを使う(例:TSH、MUG)— 単語をそのまま使わない。
- 「2026」や「SUMMER」のように変わり得る意味を含めない(本当に業務で必要なら別)。
廃止したSKUは削除しないでマークだけ無効にし、価格履歴や過去注文で参照できるようにします。SKUを置き換えたら「replaced by」参照を残し、サポートが追跡できるようにしてください。
バリデーションルールで時間をかけて壊れるのを防ぎます:販売可能アイテム全体でSKU一意制約を強制し、許可する文字を英数字とハイフンに限定し、最大長を明確に(一般的に20〜32文字)、特別ケース用にプレフィックスを予約(例:「BND-」をバンドル用に)など。AppMasterでは、データ制約とBusiness Processで保存をブロックする仕組みが適しています。
単純な在庫表示を超えた在庫ロジック
同じ「商品」が多数の販売単位を意味する場合、在庫はややこしくなります。ルールを書く前にどの単位で在庫を管理するかを決めてください:商品レベルで追うか、バリアントレベルで追うか、または両方か。
サイズや色を顧客が選ぶなら、バリアントレベルの在庫が安全です。シャツは全体として「在庫あり」でも、Small-Blueのバリアントは売り切れかもしれません。デジタルライセンスのようにバリアントが物理的な保管に影響しない場合は商品レベルで十分なこともあります。中には商品レベルはレポート用、販売はバリアントレベルという両方を持つチームもあります。
過売り防止は単一の魔法のフラグではなく、明確な状態管理の問題です。多くのカタログは予約(カートや未払い注文のために一時的に在庫を確保する)、バックオーダー(出荷日を明示して販売を許可)、在庫バッファ(同期遅延をカバーする隠し数量)、原子的な更新(注文確定と同時に在庫を減らす)を必要とします。
「謎の在庫」が生じるのは例外処理の結果です。返品は返送ラベル作成時に在庫に戻さず、検品後に在庫を戻すべきです。破損品は別ステータスやロケーションに移し、売れる在庫として表示させないようにします。在庫調整には監査証跡(誰がいつ何を変更したか)が必要です。特に複数チャネルが在庫を更新する場合は重要です。
バンドルとキットには1つの重要なルールがあります:バンドルが単なるグルーピングであるならバンドルレコードそのものを減算しないこと。コンポーネントを減算してください。3パックは同一SKUを3個減らすべきですし、キットは各コンポーネントを1つずつ減らすべきです。
例:スターターキットがボトル1本とフィルター2個を含むとします。在庫がボトル10本、フィルター15個なら、キットの可用性はフィルターにより制限されて7ではなく floor(15/2)=7、しかしボトルは10なので最小値は7になります。コンポーネントベースの計算はレポート、返品、再入荷時に一貫性を保ちます。
AppMasterでは、これはData DesignerのVariantテーブルとBusiness Process Editorの予約/減算ロジックに自然にマッピングされ、チェックアウトごとに同じルールを適用できます。
レポートを壊さないバンドルとキットのモデリング
バンドルはカタログが特別扱いに流れやすい部分です。最もシンプルなアプローチは、バンドルを通常の販売可能アイテムとしてモデル化し、構成要素の明確なリストを添えることです。
クリーンな構造は:Product(販売アイテム) と BundleLines。各BundleLineはコンポーネントSKU(またはコンポーネント商品と必要なバリアント)を指し、数量を保持します。注文は「1アイテム」として表示されますが、コスト、在庫、フルフィルメント詳細が必要なときにパーツに展開できます。
多くのバンドルは次のいずれかに当てはまります:
- 固定バンドル(kit):常に同じコンポーネントと数量。
- 設定可能バンドル:顧客が許可されたコンポーネントから選べる(注文時にLineとして保存)。
- 仮想バンドル:在庫に影響を与えないマーチャンダイジング用のグルーピング。
価格はレポートを壊しやすいポイントです。注文行に何を出すか、何がマージンや在庫レポートに入るかを先に決めてください。一般的な方法は固定バンドル価格、パーツの合計、または割引を適用した合計(例:「任意の3つ、最安値が50%オフ」)です。
在庫も同様に厳格に扱うべきです:キットは全ての必須コンポーネントが必要数量だけある場合にのみ利用可能とします。レポートのために販売を2つの見え方で保存します:
- 販売アイテム:バンドルSKU(収益、コンバージョン、マーチャンダイジング向け)
- 消費されたコンポーネント:展開されたBundleLines(在庫移動、原価、ピッキングリスト向け)
AppMasterでは、BundleテーブルとBundleLine行を用意し、チェックアウトでコンポーネントを展開してバンドル販売とコンポーネント消費を1つのトランザクションで書くBusiness Processが自然に合います。
オプションとバリアント選択のUIパターン
良いオプションUIは購入を止めさせません。悪いUIはユーザーに推測させ、エラーを出し、離脱させます。重要なのは、購買者を早い段階で「実在する買えるバリアント」へ導き、選択が何を変えるかを明確に示すことです。
顧客向け:オプションが先、バリアントは後で
信頼できるパターンは、オプション(Size、Color、Materialなど)を提示し、選択可能な組み合わせだけを表示/有効にすることです。
顧客に任意の組み合わせを選ばせて「カートに入れる」で失敗させる代わりに、ユーザーが選択するたびに不可能な組み合わせを無効化してください。例えば Color = Black を選んだら、Blackに存在しないサイズを無効(削除ではなく)にして、何が利用できないかを理解させます。
選択が変わるたびに最も重要な部分を更新します:価格(セール価格やバンドル割引を含む)、メイン画像(選択した色やスタイルに合わせる)、在庫ステータス(製品全体ではなく正確なバリアント在庫)、配送見積もり(バリアントによって異なる場合)、場合によってはサポート用のSKUや「商品コード」。
UIは落ち着かせておきましょう。オプショングループをいくつかずつ表示し、色見本やボタンが有効な場合は巨大なドロップダウンを避け、現在の選択を明確に示します。
管理者向け:スプレッドシートのようにバリアントを編集
管理画面ではスピードが重要です。各行がSKU、各列がオプションやルール(価格、バーコード、重量、在庫、有効/無効)になったバリアントグリッドが有効です。価格更新や可用性の切替、画像割り当てなどの一括操作を用意しましょう。
AppMasterで作るなら、Variantテーブルにバインドされたグリッドにフィルター(OptionValue、有効ステータス、低在庫)を付け、一括更新アクションがルール検証をして保存する仕組みが実用的です。
ステップバイステップ:バリアント選択とバンドル可用性チェック
選択フローは単純に感じられる必要がありますが、下では厳密なルールが必要です。目的は、購買者が今構成している正確なSKUを常に把握し、それが今買えるかどうかを判断することです。
バリアント選択(単一商品)
商品名や画像以上のデータをロードしてください。Option値の全セットと、SKUとして存在する有効なバリアント組み合わせのリストが必要です。
信頼できるフロー:
- 商品、オプション、既存のバリアント(または有効な組み合わせのコンパクトマップ)を取得する。
- 購買者の現在の選択を保持し(例:Size=M, Color=Black)、クリックごとに更新する。
- 変更のたびに、選択したOption値とバリアントリストを比較して一致するVariantを探す。
- 一致していて購入可能(有効、価格設定済み、ブロックされていない)なら「カートに入れる」を有効化する。
- 一致しない場合は「カートに入れる」を無効のままにし、購買者を有効な選択へ誘導する。
小さなUIの工夫で不満を防げます:先に選択があると不可能なOption値をその時点で無効化すること。Size=MはBlackでしか存在しないなら、Mが選ばれた時点で他の色を無効にしてください。
バンドル可用性(コンポーネントで作られるキット)
バンドルの「在庫あり」は単一数ではありません。コンポーネント次第です。通常のルールは:
bundle_available = 各コンポーネントについて floor(component_stock / component_qty_per_bundle) の最小値
例:スターターキットがボトル1本とフィルター2個を含む場合、ボトルが10、フィルターが9なら可用性は min(floor(10/1)=10, floor(9/2)=4) = 4 キットです。
エラーメッセージは具体的にしましょう。「在庫切れ」より「このキットはフィルターが原因で残り4個のみ利用可能」のように。AppMasterでは、このチェックはBusiness Processで表現しやすい:まず一致するVariantを決め、次にバンドル制限を計算し、UIに表示する明確なステータスを返します。
避けるべき一般的なミスと罠
カタログは「存在しうる全て」をモデリングしようとして壊れます。最短で行き詰まる方法は、可能な全オプション組み合わせを先に生成してしまい、カタログが成長するにつれてそれを掃除しようとすることです。
在庫する予定のないバリアントを作るのが典型的な罠です。色4×サイズ6×素材3で72通りあって、そのうち実際に売るのは10通りだけなら、残り62レコードは雑多さを生み、ミスを誘い、レポートを遅らせます。
価格は静かにバグを生む原因です。同じ価格がProduct、Variant、価格テーブル、プロモーテーブルに散らばると問題になります。シンプルなルール:ベース価格は1箇所に保存し、オーバーライドは本当に必要な箇所だけにする(例:あるVariantだけ価格が違う)。
バンドルとキットの在庫ミスは、バンドルとそのコンポーネントの両方を減算してしまうこと。スターターキット(ボトル1、フィルター2)を売るとき、1キットとさらに1ボトル+2フィルターを減らすと在庫が早めにゼロになります。バンドルを販売可能アイテムとして残しつつ、可用性と減算はコンポーネントから計算してください。
管理ツールは無効なデータを許すと壊れます。早めにガードレールを入れましょう:
- アーカイブ済みを含めてSKUの重複をブロックする。
- 各Variantに全てのOption値を必須にする(「サイズがない」状態を禁止)。
- 2つのVariantが同じOption組み合わせを共有しないようにする。
- バンドルコンポーネントを検証する(数量が0ではない、SKUが欠けていない)。
最後に、変更に備えましょう。後から新しいOption(例:「幅」)を追加するのはマイグレーションであり、チェックボックスではありません。既存のVariantに何が起きるか、デフォルトをどう設定するか、古い注文が元のSKUとOptionスナップショットをどう保持するかを決めておいてください。
出荷前の簡単チェックリスト
ローンチ前に現実で壊れるポイントを確認してください:SKU検索、在庫更新、不可能な購入のブロックなど。
すべての販売可能SKUが簡単に見つかることを確認します。検索は名前、SKUコード、バーコード/GTIN、主要属性(サイズや色)で発見できるべきです。倉庫でスキャンするなら、いくつか物理スキャンをテストして正確なSKUにたどり着くか確認してください。
在庫変更が発生する場所を一箇所に厳格に定めます。真の情報源(在庫移動ロジック)を1つに決め、注文、キャンセル、返品、手動調整、バンドル組み立てなどのすべてのイベントがそこを使うようにします。複数の画面やワークフローで在庫が編集できると、必ずズレが生じます。
実行する価値のあるチェック:
- UIでオプションを選んで、無効な組み合わせが早期にブロックされる(カート追加前)ことを確認する。
- バンドルでは、最も不足しているコンポーネントが可用性を決め、必要数量が考慮されることを確認する(キットに2つ必要なバッテリーは可用性を半分にする)。
- SKUを廃止して、閲覧や検索結果から消えるが過去の注文、請求書、レポートでは正しく表示されることを確認する。
- テスト注文を出してキャンセルし、再度注文して在庫がきちんと戻り再予約されることを確認する。
AppMasterで構築する場合、在庫更新ルールを1つのBusiness Processにまとめておき、すべての経路から呼び出す習慣がほとんどの在庫バグを防ぎます。
例シナリオと実務的な次のステップ
アパレルショップのカタログを例に想像してください。
Size(S、M、L)とColor(Black、White)の2つのOptionがあるTシャツを売っています。購入可能な各組み合わせは独自のVariantであり、必要に応じて独自SKU、価格、在庫を持ちます。
スキーマでは「Classic T-shirt」のProductレコードを1つ、2つのOption(Size、Color)レコード、そして複数のVariantレコードを保持します。各Variantは選択されたOption値(例:Size=M、Color=Black)とSKU(例:TSH-CL-M-BLK)を保持します。在庫はProductレベルではなくVariantレベルで追跡します。
UIでは選択肢を絞り、行き止まりを防ぎます。一般的なパターンは:まずすべてのColorを表示し、選ばれたColorに存在するSizeだけを表示する(またはその逆)。「White + L」が存在しないなら、選択不可にするか無効表示して明確な注記を表示します。
次にバンドル「Gift Set」を追加します。内容は:
- Classic T-shirt ×1(任意のVariant)
- Sticker pack ×1(単純なSKU)
バンドルの可用性は最も逼迫したコンポーネントで制限されます。Sticket pack在庫が5なら、Tシャツ在庫が十分でもバンドルは5以上売れません。もしバンドルが特定のTシャツVariant(例:Black M)を要求するなら、可用性は min(StickerPackStock, BlackMStock) です。
AppMasterでの実務的な次のステップ:
- Data Designerでテーブルを作る(Products、Options、OptionValues、Variants、VariantOptionValues、Inventory、Bundles、BundleComponents)。
- Business Process Editorで「有効なバリアントを見つける」ロジックと「バンドル可用性を計算する」ロジックを実装する。
- 同じプロジェクトからWebとネイティブモバイルのUIを生成し、同じバリアントフィルタと可用性ルールをどこでも使う。
エンドツーエンドで素早くプロトタイプしたいなら、AppMaster (appmaster.io) は実際のビジネスロジックを持つ完全なアプリを構築するために設計されており、まさにバリアント選択やバンドル在庫ルールが依存するものです。


