Jak zbudowałem system synchronizacji typu „ustaw i zapomnij”

Ceny produktów zmieniają się w wielu miejscach. Zmieniają się w panelu administracyjnym, poprzez masowe importy lub za pomocą webhooków API.

Jeśli chcesz zsynchronizować te zmiany z zewnętrznym marketplace'em, napotykasz problem. Dodawanie wywołania synchronizacji do każdej pojedynczej ścieżki kodu to błąd. O czymś zapomnisz. Coś zepsujesz. Utrzymanie systemu staje się koszmarem.

Sygnały Django rozwiązują ten problem. Podpinasz się pod zdarzenie zapisu modelu (save event). Dzięki temu wyłapujesz każdą zmianę w jednym miejscu.

Sygnały mają jednak wadę. Jeśli zaktualizujesz 100 cen naraz, sygnał zostanie wywołany 100 razy. To spowoduje 100 wywołań API. Napotkasz limity zapytań (rate limits) lub zmarnujesz zasoby.

Aby to naprawić, stosuję trzystopniowy wzorzec:

• Handler sygnału, który zamiast działać natychmiast, zbiera identyfikatory (ID). • Zbiór (set) przypisany do wątku, aby usunąć duplikaty. • Callback typu flush wykorzystujący transaction.on_commit, aby przetworzyć wszystko naraz.

Oto jak to działa.

  1. Użyj threading.local() Nie używaj zmiennej globalnej. Zmienne globalne współdzielą stan między żądaniami, co prowadzi do wycieku danych. threading.local() izoluje dane w obrębie pojedynczego wątku.

  2. Zapisuj, nie działaj Handler sygnału po prostu dodaje ID produktu do zbioru. Następnie instruuje Django, aby uruchomił funkcję flush dopiero po pomyślnym zakończeniu transakcji w bazie danych. Zapobiega to synchronizacji danych, których zapis się nie powiódł.

  3. Grupuj pracę (Batching) Gdy transakcja zostanie zatwierdzona (commit), uruchamia się funkcja flush. Kopiuje ona zbiór i go czyści, a następnie przesyła całą listę ID do warstwy serwisowej (service layer).

Warstwa serwisowa wykonuje jedno zapytanie masowe (bulk query), aby pobrać wszystkie produkty. Następnie grupuje je według sklepu. Na koniec wysyła jedno zadanie do Celery dla każdego sklepu.

Korzyści są jasne:

Budujesz system raz. Każda nowa funkcja, którą dodasz później, automatycznie współpracuje z systemem synchronizacji.

Jak Ty radzisz sobie z synchronizacją zewnętrznych API w Django? Używasz sygnałów czy innego wzorca?

Źródło: https://dev.to/acel/how-i-built-a-set-it-and-forget-it-sync-system-with-django-signals-2ld7