Formularz wysyła prośbę o utworzenie zamówienia. Serwer zapisuje je poprawnie, lecz odpowiedź ginie po drodze. Użytkownik widzi błąd i klika jeszcze raz. Czy aplikacja utworzy drugie zamówienie? To pytanie dotyczy również webhooków, importów, zadań w kolejce i automatyzacji uruchamianych według harmonogramu.

Nie wystarczy powiedzieć „spróbuj ponownie”. Najpierw trzeba ustalić, czy powtórzenie tej samej operacji może zmienić wynik. MDN nazywa idempotentnym działanie, którego zamierzony efekt po jednym i po wielu identycznych wywołaniach pozostaje taki sam. To dobra zasada projektowa, ale trzeba ją świadomie zaimplementować dla operacji tworzących nowe dane.

Identyfikator operacji zamiast zgadywania

Klient może nadać konkretnej próbie unikalny identyfikator, a serwer zapisać go wraz z wynikiem. Jeżeli to samo żądanie przyjdzie ponownie z tym samym identyfikatorem, serwer zwraca wcześniejszą odpowiedź zamiast tworzyć kolejny obiekt. Nowa, zamierzona operacja dostaje nowy identyfikator.

Ważny jest zakres klucza. Identyfikator powinien być powiązany z użytkownikiem i rodzajem działania, aby przypadkowe lub cudze żądanie nie otrzymało niewłaściwego wyniku. System powinien też rozpoznać próbę użycia tego samego klucza z innymi danymi i odpowiedzieć błędem, zamiast po cichu potraktować ją jako powtórkę.

Wynik i klucz zapisuj spójnie

Jeśli zamówienie zapisuje się osobno od informacji „ten identyfikator został obsłużony”, awaria pomiędzy tymi krokami znów otwiera drogę do duplikatu. Trzeba więc zaplanować atomowy zapis w jednej bazie albo inny mechanizm, który jasno określa stan operacji. Samo trzymanie identyfikatorów w pamięci procesu nie wystarczy po restarcie.

Przy przetwarzaniu z kolejki podobną rolę pełni identyfikator zdarzenia. Konsument może otrzymać to samo zdarzenie więcej niż raz; powinien sprawdzić, czy skutek już zapisano. Ponawianie nadal ma sens, bo chwilowy brak połączenia nie musi oznaczać trwałej porażki.

Skutki poza bazą wymagają osobnej decyzji

Wysłanie e-maila, wywołanie zewnętrznego API czy pobranie płatności nie zawsze mieści się w tej samej transakcji co lokalny zapis. Powstaje trudny moment: zewnętrzna usługa mogła wykonać polecenie, choć Twoja aplikacja nie dostała odpowiedzi. Dla każdej takiej integracji sprawdź, czy obsługuje własny klucz ponowienia, jak zweryfikować status i kiedy potrzebna jest ręczna interwencja.

Nie obiecywałbym „dokładnie jednego wykonania” całego rozproszonego procesu bez wskazania granic tej gwarancji. Rozsądniejszy cel brzmi: powtórne żądanie daje jeden zamierzony skutek tam, gdzie mamy kontrolę, a sytuacje niepewne są widoczne i możliwe do wyjaśnienia.

Mały test, który ujawnia duży problem

Wyślij to samo żądanie dwa razy, jednocześnie i po krótkiej przerwie. Potem przerwij połączenie po stronie klienta tuż po wysłaniu pierwszego żądania i ponów je z tym samym identyfikatorem. Sprawdź bazę, kolejkę, wiadomości i wynik w interfejsie. Powinieneś zobaczyć jeden zamierzony obiekt oraz jasną odpowiedź na ponowienie.

Bezpieczne ponawianie nie jest dodatkiem na końcu projektu. To część ścieżki użytkownika, która decyduje, czy awaria połączenia zamieni się w chwilową niedogodność, czy w realny bałagan w danych.

Źródła i dalsza lektura

  1. developer.mozilla.org

Zobacz też

Jak łączę narzędzia i automatyzuję powtarzalną pracę

Powtarzasz te same kroki co tydzień? Napisz, z jakich narzędzi korzystasz i co chcesz, żeby działo się samo.

Opisz powtarzalne zadanie
Masz pytanie dotyczące swojego projektu?Porozmawiajmy