「一度設定すればあとはお任せ」な同期システムの構築方法

商品価格はさまざまな場所で変更されます。管理パネル、一括インポート、あるいはAPIのWebhook経由などです。

これらの変更を外部のマーケットプレイスに同期したい場合、問題に直面します。すべてのコードパスに同期処理を追加するのは間違いです。必ずどれかを忘れたり、壊したりしてしまいます。メンテナンスが困難な悪夢へと変わります。

Django signalsを使えば、この問題は解決できます。モデルの保存(save)イベントにフックすることで、あらゆる変更を一箇所でキャッチできます。

しかし、signalsには欠点があります。一度に100個の価格を更新すると、signalが100回実行されます。これにより100回のAPIコールが発生し、レート制限に抵触したり、リソースを無駄にしたりすることになります。

私はこれを解決するために、3つの要素からなるパターンを使用しています。

• 即座に実行するのではなく、IDを収集するsignal handler。 • 重複を排除するためのスレッドごとのset。 • すべてを一度に処理するための、transaction.on_commitを使用したflushコールバック。

仕組みは以下の通りです。

  1. threading.local() を使用する グローバル変数を使用してはいけません。グローバル変数はリクエスト間で状態を共有してしまうため、データの漏洩につながります。threading.local() を使えば、データを単一のスレッド内に隔離できます。

  2. 実行するのではなく、記録する signal handlerは、単に製品IDをsetに追加するだけです。その後、データベースのトランザクションが成功した後にのみflush関数を実行するようDjangoに指示します。これにより、保存に失敗したデータの同期を防ぐことができます。

  3. 処理をバッチ化する トランザクションがコミットされると、flush関数が実行されます。この関数はsetをコピーして中身をクリアし、その後、IDのリスト全体をサービスレイヤーに送信します。

サービスレイヤーは、すべての製品を取得するために1回のバルククエリを実行します。それらを店舗ごとにグループ化し、最終的に店舗ごとに1つのタスクをCeleryに送信します。

メリットは明らかです。

システムは一度構築すれば完了です。後から追加する新しい機能も、自動的に同期システムと連携します。

あなたはDjangoで外部APIの同期をどのように扱っていますか?signalsを使っていますか、それとも別のパターンを使っていますか?

出典: https://dev.to/acel/how-i-built-a-set-it-and-forget-it-sync-system-with-django-signals-2ld7