Przejdź do głównej zawartości

Zapisy i idempotencja

Dla: Programista

Zapisy w API to mutacje GraphQL wysyłane na ten sam adres co zapytania. Każda mutacja wymaga zakresu zapisu z jej strony w referencji, a zapisane zmiany widać w Prynt z integracją jako autorem.

Połączenie może zerwać się po wysłaniu mutacji, ale przed odebraniem odpowiedzi. Nie wiesz wtedy, czy zapis się wykonał. Ponowienie bez zabezpieczenia może utworzyć drugi taki sam obiekt, np. drugie zamówienie. Nagłówek Idempotency-Key rozwiązuje ten problem: żądanie ponowione z tym samym kluczem w ciągu 24 h nie wykona się drugi raz (w normalnych warunkach pracy API, zobacz zasady niżej).

Przykłady pokazują adres zastępczy. Wpisz adres swojej drukarni, żeby go podmienić.

  1. Przed wysłaniem mutacji wygeneruj klucz, najlepiej UUID, i zapisz go razem z operacją, którą wykonujesz (np. przy zamówieniu w Twoim sklepie).

  2. Wyślij mutację z nagłówkiem Idempotency-Key:

    Okno terminala
    IDEMPOTENCY_KEY=$(uuidgen) # zapisz go, zanim wyślesz żądanie
    curl -sS https://twoja-drukarnia.prynt.pl/api/v1/graphql \
    -H "Authorization: Bearer $PRYNT_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
    --data-binary @mutacja.json
  3. Gdy nie dostaniesz odpowiedzi, wyślij to samo żądanie z tym samym kluczem. Jeśli pierwsze się wykonało, API zwróci jego zapisaną odpowiedź z nagłówkiem Idempotent-Replayed: true i niczego nie zapisze ponownie.

  • Wartość klucza: od 1 do 255 drukowalnych znaków ASCII. Inaczej API odpowiada błędem API_IDEMPOTENCY_KEY_INVALID.
  • Tylko mutacje. Zapytania ignorują nagłówek: odczyt można ponawiać bez niego.
  • Jeden klucz, jedna operacja. Nowa operacja zawsze dostaje nowy klucz. Klucze każdej integracji są liczone osobno.
  • 24 h. API pamięta klucz przez 24 h od zakończenia mutacji. Po tym czasie ten sam klucz działa jak nowy, więc późniejsze ponowienie może wykonać zapis drugi raz.
  • Długie mutacje. Mutacja, która trwa dłużej niż 60 minut, przestaje po tym czasie chronić swój klucz: ponowienie może ją wtedy wykonać drugi raz.
  • Gdy Prynt nie może pamiętać kluczy. Jeśli przy chwilowej awarii API nie może zarezerwować klucza przed wykonaniem mutacji, nie wykonuje jej: odpowiada statusem HTTP 503 i błędem API_IDEMPOTENCY_UNAVAILABLE. Nic się wtedy nie zapisało, więc ponów żądanie później z tym samym kluczem i tą samą treścią. Mutacja bez nagłówka Idempotency-Key wykonuje się w takiej sytuacji normalnie, bez ochrony.
  • Normalne warunki pracy. Ochronę zapewniamy w normalnych warunkach pracy API. Przy awarii po stronie Prynt w trakcie mutacji albo tuż po niej API może nie zapamiętać klucza, nawet gdy dostaniesz zwykłą odpowiedź: ponowienie przez kilka minut dostaje API_IDEMPOTENCY_IN_PROGRESS, a potem może wykonać mutację drugi raz. Dlatego integracja, dla której duplikat byłby kosztowny, po takim ponowieniu sprawdza zapytaniem, czy go nie ma.
  • Ta sama treść. Żądania są „takie same”, gdy mają tę samą operację, nazwę operacji i zmienne. Formatowanie i komentarze w zapytaniu ani kolejność kluczy w variables nie mają znaczenia; każda inna zmiana tak.

Konflikty mają status HTTP 409, a mutacja nie jest wtedy wykonywana.

Sytuacja Odpowiedź
Mutacja z tym kluczem już się wykonała, ta sama treść Zapisana odpowiedź pierwszego żądania i nagłówek Idempotent-Replayed: true.
Mutacja z tym kluczem jeszcze trwa CONFLICT, API_IDEMPOTENCY_IN_PROGRESS. Ponów za chwilę z tym samym kluczem.
Klucz był użyty z inną treścią CONFLICT, API_IDEMPOTENCY_KEY_REUSED. To błąd w kodzie integracji: nowa operacja, nowy klucz.
Mutacja się wykonała, ale odpowiedź miała około 64 KiB lub więcej CONFLICT, API_IDEMPOTENCY_RESPONSE_TOO_LARGE. Nie ponawiaj; pobierz wynik zapytaniem.

API zapamiętuje odpowiedź zawsze, gdy mutacja mogła coś zapisać: przy sukcesie i przy częściowym sukcesie, gdy obok danych są błędy. Powtórka zwraca wtedy te same dane i te same błędy.

Klucz jest zwalniany tylko wtedy, gdy odpowiedź dowodzi, że nic się nie wykonało: żądanie odrzucono przed wykonaniem (np. błąd zapytania, brak zakresu, przekroczony limit) albo jedyna mutacja w żądaniu zakończyła się błędem. Możesz wtedy poprawić żądanie i wysłać je z tym samym kluczem.

Wysyłaj jedną mutację na żądanie. Gdy żądanie ma kilka mutacji i jedna z nich zakończy się błędem, API nie może wykluczyć, że pozostałe coś zapisały: klucz zostaje zajęty, a powtórka zwraca tę samą odpowiedź z błędem.

Mutacje zmieniające rekord, np. updateOrder, updateClient czy updateClientAddress, to łatki: w ich typie wejścia każde pole jest opcjonalne, a pole, którego nie wysyłasz, zostaje bez zmian. null czyści pole tylko tam, gdzie opis pola w referencji to podaje (np. telefon klienta); w pozostałych polach API go odrzuca błędem walidacji. Dlatego puste wartości ze swojego systemu pomijaj, a null wysyłaj tylko wtedy, gdy chcesz pole wyczyścić. Poprawność rekordu API sprawdza po zmianie: firma zachowuje NIP, a kod pocztowy adresu musi pasować do kraju, który adres ma po zmianie. Tak samo zmieniasz operację zamówienia (updateOperation): jej zasady i odmowy opisuje Technologia zamówienia.

Idempotency-Key chroni jedno wywołanie przez 24 h. Żeby na stałe powiązać rekord w Prynt z rekordem w swoim systemie, nadaj mu własny identyfikator w polu externalRef (dziś: klient i zamówienie zakupu). Przy kolejnej synchronizacji znajdziesz po nim rekord filtrem externalRef i zapiszesz go mutacją zmiany zamiast tworzyć drugi (upsert). Identyfikator jest unikalny i widoczny tylko w obrębie Twojej integracji: inna integracja ma własne identyfikatory tych samych rekordów. Przykłady: Klienci do CRM i z powrotem i Zamówienie zakupu z systemu dostawcy.

Ponawiaj tylko błędy przejściowe, z rosnącym odstępem:

Ponawiaj Nie ponawiaj bez zmian w żądaniu
Błąd sieci, przekroczony czas oczekiwania, odpowiedź HTTP 5xx VALIDATION, BAD_REQUEST
INTERNAL UNAUTHENTICATED, FORBIDDEN
RATE_LIMITED — po czasie z nagłówka Retry-After NOT_FOUND
API_IDEMPOTENCY_IN_PROGRESS, API_IDEMPOTENCY_UNAVAILABLE Pozostałe CONFLICT
  • Odstęp rośnie wykładniczo z losowym rozrzutem, np. 1, 2, 4, 8 sekund, najwyżej 30 sekund i kilka prób.
  • Ponowiona mutacja ma ten sam Idempotency-Key i tę samą treść.
  • Gotowy kod w czterech językach: Obsługa błędów i ponowień.