Как я создал систему синхронизации по принципу «настрой и забудь»
Цены на товары меняются в разных местах. Они меняются в панели администратора, через массовый импорт или через API webhooks.
Если вы хотите синхронизировать эти изменения с внешним маркетплейсом, вы сталкиваетесь с проблемой. Добавлять вызов синхронизации в каждый отдельный путь в коде — это ошибка. Вы обязательно про что-то забудете. Вы что-нибудь сломаете. Поддержка превратится в кошмар.
Django signals решают эту проблему. Вы подключаетесь к событию сохранения модели (save event). Это позволяет отлавливать любые изменения в одном месте.
Но у сигналов есть недостаток. Если вы обновите 100 цен одновременно, сигнал сработает 100 раз. Это вызовет 100 API-запросов. Вы либо упретесь в лимиты (rate limits), либо впустую потратите ресурсы.
Чтобы это исправить, я использую паттерн из трех частей:
• Обработчик сигналов, который собирает ID вместо того, чтобы действовать немедленно.
• Множество (set) для каждого потока, чтобы удалить дубликаты.
• Callback-функция очистки (flush callback), использующая transaction.on_commit, чтобы обработать всё разом.
Вот как это работает.
Используйте
threading.local()Не используйте глобальные переменные. Глобальные переменные разделяют состояние между запросами. Это приводит к утечке данных.threading.local()изолирует данные внутри одного потока.Записывайте, а не действуйте Обработчик сигнала просто добавляет ID продукта в
set. Затем он дает Django команду запустить функцию очистки (flush function) только после успешного завершения транзакции базы данных. Это предотвращает синхронизацию данных, которые не удалось сохранить.Группируйте работу (Batching) Когда транзакция фиксируется (commit), запускается функция очистки. Она копирует множество и очищает его. Затем она отправляет весь список ID в сервисный слой (service layer).
Сервисный слой выполняет один массовый запрос (bulk query), чтобы получить все продукты. Он группирует их по магазинам. Наконец, он отправляет по одной задаче в Celery для каждого магазина.
Преимущества очевидны:
- Дедупликация происходит автоматически. Множество (
set) берет это на себя. - Безопасность транзакций встроена. Вы никогда не синхронизируете данные, транзакция которых была откачена (rolled-back).
- Высокая эффективность. Вы избегаете проблемы N+1 запросов.
- Высокая надежность. Celery берет на себя повторные попытки (retries), если API выдаст ошибку.
Вы создаете систему один раз. Любая новая функция, которую вы добавите позже, будет автоматически работать с системой синхронизации.
А как вы обрабатываете синхронизацию с внешними API в Django? Используете ли вы сигналы или другой паттерн?
Источник: https://dev.to/acel/how-i-built-a-set-it-and-forget-it-sync-system-with-django-signals-2ld7