𝗛𝗼𝘄 𝗜 𝗕𝘂𝗶𝗹𝘁 𝗮 𝗦𝗲𝘁 𝗜𝘁 𝗮𝗻𝗱 𝗙𝗼𝗿𝗴𝗲𝘁 𝗜𝘁 𝗦𝘆𝗻𝗰 𝗦𝘆𝘀𝘁𝗲𝗺

Product prices change in many places. They change in the admin panel, via bulk imports, or through API webhooks.

If you want to sync these changes to an external marketplace, you face a problem. Adding a sync call to every single code path is a mistake. You will forget one. You will break one. Maintenance becomes a nightmare.

Django signals solve this. You hook into the model save event. This catches every change in one place.

But signals have a flaw. If you update 100 prices at once, the signal fires 100 times. This triggers 100 API calls. You will hit rate limits or waste resources.

I use a three-part pattern to fix this:

• A signal handler that collects IDs instead of acting immediately. • A per-thread set to remove duplicates. • A flush callback using transaction.on_commit to process everything at once.

Here is how it works.

  1. Use threading.local() Do not use a global variable. Global variables share state across requests. This leads to data leakage. threading.local() keeps data isolated to a single thread.

  2. Record, don't act The signal handler simply adds the product ID to a set. It then tells Django to run a flush function only after the database transaction succeeds. This prevents syncing data that failed to save.

  3. Batch the work When the transaction commits, the flush function runs. It copies the set and clears it. Then it sends the entire list of IDs to a service layer.

The service layer performs one bulk query to fetch all products. It groups them by store. Finally, it sends one single task to Celery per store.

The benefits are clear:

You build the system once. Every new feature you add later works with the sync system automatically.

How do you handle external API syncs in Django? Do you use signals or a different pattern?

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