定義でルールを組む — 条件×特典のカートリッジ型 販促ルールエンジン
販促を「条件」と「特典」の部品の組み合わせとして表現し、開発を待たずに施策を追加する。
PROBLEM販促は「実装」ではなく「設定」でありたい
通販の基幹エンジンでは、季節ごと・チャネルごとに販促施策が絶えず入れ替わる。送料無料、初回客限定の割引、まとめ買いポイント増量。これらを一件ずつコードで実装していると、施策の追加が常に開発リードタイムに縛られ、リリース待ちの行列ができる。狙いは、販促を データ(定義) として宣言的に表現し、既存の部品の組み合わせで新施策を立ち上げられる状態にすることだ。
そこで販促を「条件カートリッジ × 特典カートリッジ + パラメータ」という定義として持つ。カートリッジとは差し替え可能なプラグイン部品であり、汎用的なロジックを一つ実装しておけば、あとはパラメータ違いで無数の施策として再利用できる。
MODEL販促 = 条件カートリッジ × 特典カートリッジ
一つの販促定義は、成立条件を判定する条件カートリッジ群と、成立時に何を与えるかを表す特典カートリッジ群の掛け合わせで構成される。どちらも本体はロジックを持たない「枠」で、具体的な閾値や割引率はパラメータとして外から注入する。
- 条件カートリッジ:カート金額が閾値以上か、初回客か、対象カテゴリを含むか、といった判定を行う。
- 特典カートリッジ:送料割引、値引き明細の追加、ポイント付与など「何を与えるか」を表す。
- パラメータ:閾値・率・上限・対象など、同じカートリッジを別施策として振る舞わせる値。
PIPELINE評価パイプライン:判定してから書き込む
エンジンは施策を二段階で評価する。まず条件カートリッジの evaluate() が適用可否 judge を返す。当選した販促だけをグループと優先度で選抜し、同一グループ内の重複適用を排他する。ここで初めて特典カートリッジの evaluate() を呼び、割引明細やポイント付与といった「書き込み指示 data」を生成する。
- 定義ロード:対象となる販促定義を一括で読み込む。
- 条件評価:各条件カートリッジが judge を返し、成立/不成立を確定。
- 選抜:グループ・優先度で当選販促を絞り、重複適用を排他する。
- 特典評価:当選分のみ書き込み指示 data を生成する。
IDEMPOTENTモードと冪等性:取消・再計算に強くする
エンジンは同じ定義に対して複数のモードで動く。判定のみはプレビュー用で反映しない。確定は結果を反映し、取消と差戻しで巻き戻す。ポイントや割引の付与は毎回まっさらから作るのではなく、前回スナップショットとの 差分 で計算する。これにより再実行しても結果が二重にならず、取消・再計算が冪等になる。注文内容が後から変わっても、差分ベースなら「あるべき状態」との差だけを反映すればよい。
class FirstTimeCustomerCondition:
# 初回客かを判定する条件カートリッジ
def evaluate(self, ctx, params):
threshold = params.get("max_past_orders", 0)
judge = ctx.customer.past_order_count <= threshold
return {"judge": judge}
class ShippingDiscountBenefit:
# 送料割引の書き込み指示を生成する特典カートリッジ
def evaluate(self, ctx, params):
rate = params.get("rate", 1.0) # 1.0 で送料全額割引
amount = round(ctx.cart.shipping_fee * rate)
return {"data": [
{"type": "discount", "target": "shipping", "amount": amount},
]}TRADEOFF利点と、払うべきコスト
この形の強みは明快だ。新施策を「設定」で追加でき開発待ちが減る。条件×特典の組み合わせは表現力が高く、優先度・グループによる既存施策との整合をエンジンが一元管理する。一方でコストもある。カートリッジの粒度設計とパラメータ仕様の初期整備には相応の労力がかかり、最初の数枚を作り切るまでは投資が先行する。さらに条件と特典の組み合わせは容易に爆発するため、テストは代表的な組み合わせを選んで守る戦略が要る。柔軟さの代償として、全網羅ではなくリスクベースの検証に振り切る判断が求められる。
販促を「条件カートリッジ × 特典カートリッジ + パラメータ」という定義で表し、判定と書き込みを分離し差分で反映すれば、施策追加は設定作業になり取消・再計算も冪等になる。初期整備と組合せテストのコストを織り込めるなら、開発待ちを解消する強力な選択肢になる。