小規模EC向けWMS連携
倉庫管理システムを導入すべきかという課題は、多くの小規模EC事業者が予想するよりも早く訪れます。スプレッドシートでも当初は十分に対応できますが、限界が来るきっかけは、注文数よりも注文の複雑さであることが一般的です。例えば、複数SKUの商品バリエーション管理、セット商品、複数のフルフィルメント拠点にまたがる欠品商品の受注、購入手続き時の正確な引当可能在庫の把握です。本記事では、小規模EC事業にGreaterWMSを連携した当社の経験をもとに、うまくいった点、想定以上に設計が必要だった点、スプレッドシートのほうが適している場面を紹介します。
GreaterWMSを選んだ理由
GreaterWMSは、REST API、Djangoのバックエンド、Vueのフロントエンドを備えたオープンソースの倉庫管理システムです。複数倉庫の在庫管理、ロット・バッチ追跡、入出庫オーダー管理、基本的なレポート機能を提供します。コードベースは積極的に保守されており、APIドキュメントは網羅的ではないものの、主要な操作を説明しています。
ホスト型の代替サービスではなくGreaterWMSを選んだ理由は2つあります。まず、すでにセルフホスト型のインフラを運用しており、規模拡大に伴って注文ごとに課金されるSaaSへの依存を増やしたくありませんでした。次に、オープンソースであれば、ベンダーの機能追加を待たずにデータモデルを拡張できるからです。ロットごとの有効期限管理に特有の要件がある小規模事業にとって、この柔軟性は重要でした。
当社のデプロイ構成は、Djangoアプリケーション、PostgreSQL、Redisを組み合わせたDocker Composeスタックです。上流プロジェクトのComposeファイルに含まれるのはフロントエンドとバックエンドのコンテナのみで、デフォルト設定ではSQLiteを使います。小規模なVPSでも、他のサービスと同時に余裕を持って動作します。初期セットアップは数時間で済みますが、連携作業にははるかに長い時間がかかります。
API連携の設計パターン
GreaterWMSはトークンベースの認証を使います。各ユーザーはトークン(openidの値)を持ち、AuthorizationヘッダーにBearerトークンとして渡すのではなく、独自のtokenリクエストヘッダーで渡します。OAuthフローやAPIキーのローテーション機能は組み込まれていないため、管理者アカウントを流用せず、最小限の権限を持つ専用サービスユーザーを作成すべきです。
APIはおおむねRESTに準拠し、JSON形式のデータを扱います。EC連携で主に操作するリソースは、goods(WMS上の商品カタログ)、inventory(倉庫・商品ごとの現在の在庫数)、inbound orders(発注や返品の入庫オーダー)、outbound orders(顧客注文を出荷するための出庫オーダー)です。
早い段階で決めた原則は、購入手続きの処理経路からWMS APIを同期的に呼び出さないことです。WMSを取引成立に不可欠な処理経路に置かず、応答が遅れたり一時的に使えなくなったりしても、顧客が購入を完了できるようにします。WMSとのやり取りは、すべて非同期キューを介します。購入手続きで注文を主データベースに記録し、イベントを発行します。バックグラウンドワーカーがイベントを受け取り、GreaterWMSに出庫オーダーを作成します。ワーカーの処理に失敗した場合は指数バックオフで再試行し、失敗が続けばアラートを出します。この疎結合化は必須です。
注文同期
出庫オーダーの作成APIには、商品ID、数量、配送先住所を渡します。EC注文の明細とWMSの商品IDの対応関係は、明示的に管理する必要があります。当社は、カタログデータベースの各商品バリエーションに追加フィールドとしてWMSの商品IDを保存しています。新しい商品を作成した際に手動で対応付けを設定し、同期ワーカーが出庫オーダー作成リクエストを送る前に確認します。
ステータス同期は逆方向です。GreaterWMSは倉庫の処理に応じて出庫オーダーのステータスを更新し、ECシステムにもそれを反映する必要があります。GreaterWMSはステータス変更をプッシュ通知しないため、ポーリングを行います。当社では数分ごとにポーリングジョブを実行し、「in progress」の出庫オーダーを照会して、「dispatched」または「cancelled」に変わっていないか確認します。変更されていれば、ECデータベースの該当注文を更新し、顧客への通知メールや追跡番号の更新など、後続の処理を実行します。
出庫オーダーの作成では、冪等性が重要です。一時的な失敗の後にバックグラウンドワーカーが再試行しても、GreaterWMSに出庫オーダーを重複作成してはいけません。当社は作成APIの呼び出しが成功した直後に、WMSの出庫オーダーIDを社内の注文レコードに保存します。再試行時には、ワーカーが作成APIを呼ぶ前にWMS IDの有無を確認します。IDがあれば作成を省略し、実行しようとしていた後続処理に進みます。この確認では、GreaterWMSで作成APIの呼び出しが成功しても、IDを保存する前に応答が失われるケースには対応できません。その場合は、再試行する前にWMSとの照合が必要です。
在庫更新
ECの在庫数とWMSを同期する部分は、運用中に最も継続的な注意が必要です。同期には2つの方向があります。ECプラットフォームには在庫数を超えた販売を防ぐために正確な在庫情報が必要であり、WMSには、その外部で発生した購入や返品を反映する必要があります。
在庫数の取得では、GreaterWMSの在庫APIから定期的に在庫スナップショットを取得し、ECサイトが参照するRedisキャッシュに書き込みます。ポーリング間隔は販売ペースによって決まります。動きの遅い商品なら1時間ごとで十分です。短時間に集中して売れる商品では、間隔を短くするか、在庫を引き当てる仕組みが必要です。後者では、顧客が商品をカートに入れると、WMSの同期周期にかかわらず在庫を一時的に確保します。
最も複雑なのは返品です。返品商品は在庫に戻されることも、検品のため隔離場所に送られることも、廃棄処理されることもあります。GreaterWMSには返品用の入庫フローがありますが、EC側の返品状態とWMSの入庫オーダー状態の対応付けは、明確に設計する必要があります。当社は、ECシステムの返品理由コードをWMSの処理区分コードに変換する状態機械を作成しました。新しい返品理由を追加するたびに、倉庫チームと対応関係を確認します。この対応付けがないと、返品された商品が利用可能な在庫に戻らず、気付かれないままになります。
競合状態とバッファ在庫
ポーリングによる在庫同期でも、競合状態は発生し得ます。2つの注文が同時に届き、どちらもWMSに反映される前に同じキャッシュ上の在庫数を読むと、ポーリング間隔内に売れた数量分だけ在庫を超過して販売する可能性があります。限定商品やロットの制約が厳しい生鮮品など、在庫超過の販売を許容できない商品では、購入手続き時にローカルの引当可能数を減らし、WMSと非同期で照合する引当層を設けるのが適切です。
より簡単な対策は、バッファ在庫です。WMSの在庫数から少量の予備在庫を差し引いた数を、ECデータベースの販売可能数として管理します。注文のたびにこのバッファを減らし、ゼロになったら、WMS上に在庫が残っていても商品を在庫切れと表示します。顧客が注文できない在庫を抱える小さなリスクはありますが、在庫超過の販売リスクを減らせて、引当ロジックも不要です。多くの小規模事業では、このバッファ方式が適切な妥協点になります。
WMSを導入すべき場合とスプレッドシートで十分な場合
倉庫管理システムを導入すると、連携の複雑さや運用負荷が増え、保守対象のシステムも1つ増えます。連携に着手する前に、解決したい問題が本当にWMSを必要とするものか、率直に見極めることが大切です。
SKU数が少なく安定している、倉庫やフルフィルメント拠点が1つだけ、少量の注文を手作業で処理している、購入手続き時にリアルタイムの引当可能在庫が不要、といった場合はスプレッドシートで十分です。この場合、WMS連携の構築・保守コストは、それによって解決できる問題の価値を上回ります。
複数の倉庫やピッキング場所がある、規制遵守や品質管理のためにロット・バッチ追跡が必要、返品が多く処理区分を体系的に管理する必要がある、受注処理を自動化しており在庫超過の販売を防ぐ正確な引当が必要、といった場合にWMSは効果を発揮し始めます。こうした運用上の問題には、スプレッドシートでは実用的な水準の厳密さで対応できません。
本記事で紹介した連携作業には、テストを含めて数週間のエンジニアリング工数がかかりました。その時間に相当する費用があれば、倉庫調整を手作業で行う業務にもかなりの時間を充てられたはずです。着手前に、どの問題を解決するのか、ソフトウェアがそのための適切な手段なのかを明確にしてください。