Як я побудував систему синхронізації за принципом «налаштував і забув»

Ціни на товари змінюються в багатьох місцях. Вони змінюються в адмін-панелі, через масовий імпорт або через API webhooks.

Якщо ви хочете синхронізувати ці зміни із зовнішнім маркетплейсом, ви стикаєтеся з проблемою. Додавати виклик синхронізації в кожен окремий шлях коду — це помилка. Ви про щось забудете. Ви щось зламаєте. Підтримка перетвориться на кошмар.

Django signals вирішують цю проблему. Ви підключаєтеся до події збереження моделі (model save event). Це дозволяє перехоплювати кожну зміну в одному місці.

Але у сигналів є недолік. Якщо ви оновлюєте 100 цін одночасно, сигнал спрацьовує 100 разів. Це викликає 100 API-викликів. Ви упретеся в ліміти запитів (rate limits) або витратите ресурси даремно.

Щоб виправити це, я використовую трикомпонентний патерн:

• Обробник сигналів (signal handler), який збирає ID замість того, щоб діяти негайно. • Множину (set) для кожного потоку, щоб видалити дублікати. • Callback-функцію очищення (flush callback), що використовує transaction.on_commit для обробки всього за один раз.

Ось як це працює.

  1. Використовуйте threading.local() Не використовуйте глобальну змінну. Глобальні змінні ділять стан між запитами. Це призводить до витоку даних. threading.local() ізолює дані в межах одного потоку.

  2. Записуйте, а не дійте Обробник сигналів просто додає ID продукту до множини (set). Потім він каже Django запустити функцію очищення (flush function) лише після того, як транзакція бази даних завершиться успішно. Це запобігає синхронізації даних, які не вдалося зберегти.

  3. Групуйте роботу (Batch the work) Коли транзакція завершується (commits), запускається функція очищення. Вона копіює множину і очищує її. Потім вона надсилає весь список ID у сервісний шар (service layer).

Сервісний шар виконує один масовий запит (bulk query), щоб отримати всі товари. Він групує їх за магазином. Нарешті, він надсилає одне єдине завдання в Celery для кожного магазину.

Переваги очевидні:

Ви будуєте систему один раз. Кожна нова функція, яку ви додасте пізніше, автоматично працюватиме з системою синхронізації.

Як ви обробляєте синхронізацію із зовнішніми API в Django? Ви використовуєте сигнали чи інший патерн?

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