Bezpieczna integracja z zewnętrznymi API, która utrzymuje działanie aplikacji podczas awarii. Naucz się timeoutów, retry, circuit breakerów i szybkich rozwiązań.

Zewnętrzne API mogą zawodzić w sposób, który nie wygląda jak jednoznaczna awaria. Najczęstszy problem to spowolnienie: żądania wiszą, odpowiedzi przychodzą z opóźnieniem, a aplikacja nadal czeka. Jeśli te wywołania znajdują się na ścieżce krytycznej, mały problem poza twoją kontrolą narasta wewnątrz systemu.
W ten sposób lokalne spowolnienie zamienia się w pełną awarię. Wątki lub workerzy utkną w oczekiwaniu, kolejki rosną, transakcje w bazie danych pozostają otwarte dłużej, a nowe żądania zaczynają przekraczać limity czasowe. Wkrótce nawet strony, które nie korzystają z zewnętrznego API, wydają się zepsute, bo system jest przeciążony oczekującą pracą.
Skutki są konkretne. Niestabilny dostawca tożsamości blokuje rejestracje i logowania. Timeout w bramce płatności zamraża koszyk, pozostawiając użytkowników w niepewności, czy zostali obciążeni. Opóźnienia w wiadomościach zatrzymują reset haseł i potwierdzenia zamówień, co wywołuje kolejną falę retry i zgłoszeń do wsparcia.
Cel jest prosty: izolować błędy zewnętrzne tak, by podstawowe procesy mogły działać dalej. To może oznaczać pozwolenie użytkownikowi na złożenie zamówienia, podczas gdy płatność zostanie potwierdzona później, albo dopuszczenie rejestracji, nawet jeśli e-mail powitalny nie zostanie wysłany.
Praktyczny miernik sukcesu: gdy dostawca jest wolny lub niedostępny, twoja aplikacja powinna nadal reagować szybko i jasno, a obszar wpływu powinien pozostać mały. Na przykład większość kluczowych żądań nadal powinna mieścić się w normalnym budżecie opóźnień, błędy powinny być ograniczone do funkcji zależnych od tego API, użytkownicy powinni widzieć jasny status (w kolejce, oczekuje, spróbuj później), a odzyskiwanie powinno następować automatycznie, gdy dostawca wraca.
Większość awarii jest przewidywalna, choć nie zawsze wiadomo kiedy wystąpi. Wymień je od razu i zdecydujesz, co warto ponawiać, a co przerwać i co pokazać użytkownikowi.
Typowe kategorie:
Nie wszystkie błędy znaczą to samo. Problemy przejściowe często warto ponowić, bo następne wywołanie może się udać (przerywane łącze, timeouty, 502/503, a czasem 429 po odczekaniu). Problemy trwałe zwykle same się nie naprawią (nieprawidłowe poświadczenia, błędne endpointy, źle sformułowane żądania, odmowa uprawnień).
Traktowanie każdego błędu jednakowo zamienia mały incydent w przestój. Ponawianie prób przy błędach trwałych marnuje czas, szybciej uderza w limity i tworzy kolejkę, która spowalnia wszystko. Nigdy nieponawianie przy problemach przejściowych zmusza użytkowników do powtarzania czynności i porzuca pracę, która mogłaby się zakończyć chwilę później.
Zwróć szczególną uwagę na procesy, w których zatrzymanie odbierane jest jak awaria: checkout, logowanie, reset hasła i powiadomienia (email/SMS/push). Dwusekundowy wzrost latencji w API marketingowym jest irytujący. Dwusekundowy wzrost w autoryzacji płatności blokuje przychody.
Pomocny test brzmi: "Czy to wywołanie musi zakończyć się teraz, by użytkownik mógł wykonać swoje główne zadanie?" Jeśli tak, potrzebujesz krótkich timeoutów, ostrożnych retry i jasnej ścieżki obsługi błędu. Jeśli nie, przesuń je do kolejki i zachowaj responsywność aplikacji.
Timeout to maksymalny czas, który chcesz poczekać, zanim przerwiesz operację i pójdziesz dalej. Bez jasnego limitu jeden wolny dostawca może zablokować czekające żądania i zatrzymać ważną pracę.
Pomaga rozróżnić dwa rodzaje oczekiwania:
Wybór liczb nie polega na perfekcji. Chodzi o dopasowanie do ludzkiej cierpliwości i charakteru przepływu.
Praktyczny sposób wyboru timeoutów to cofnięcie się od doświadczenia:
Trzeba wyważyć. Zbyt długi timeout blokuje wątki, workerów i połączenia do bazy. Zbyt krótki powoduje fałszywe błędy i niepotrzebne retry.
Retry pomagają, gdy błąd jest prawdopodobnie przejściowy: chwilowy problem sieci, hiccup DNS lub jednorazowe 500/502/503. W takich przypadkach druga próba może się udać i użytkownicy tego nie zauważą.
Ryzyko to fala retry. Gdy wielu klientów jednocześnie zawodzi i wszyscy retryują, mogą przeciążyć dostawcę (i Twoich workerów). Backoff i jitter temu zapobiegają.
Budżet retry utrzymuje dyscyplinę. Trzymaj niską liczbę prób i ogranicz łączny czas, aby podstawowe procesy nie utknęły czekając na kogoś innego.
Nie retryuj przewidywalnych błędów klienta jak 400/422 (walidacja), 401/403 (auth) czy 404. Te najpewniej znów się nie powiodą i tylko zwiększą obciążenie.
Jeszcze jedna zasada: retryuj zapisy (POST/PUT) tylko gdy masz idempotencję, inaczej ryzykujesz podwójne obciążenia lub duplikaty rekordów.
Idempotencja oznacza, że możesz wykonać to samo żądanie wielokrotnie i otrzymać ten sam końcowy rezultat. To ważne, bo retry są normalne: sieć upada, serwery restartują się, klienci timeoutują. Bez idempotencji "pomocne" ponowienie tworzy duplikaty i realne problemy finansowe.
Wyobraź sobie checkout: API płatności jest wolne, aplikacja timeoutuje i retryujesz. Jeśli pierwsze wywołanie faktycznie się powiodło, retry może stworzyć drugie obciążenie. To samo dotyczy tworzenia zamówienia, rozpoczęcia subskrypcji, wysyłania e-maila/SMS, wystawiania zwrotu czy tworzenia zgłoszenia.
Rozwiązaniem jest dołączenie klucza idempotencji (lub ID żądania) do każdego wywołania "zrób coś". Powinien być unikalny dla akcji użytkownika, nie dla próby. Dostawca (lub twoja usługa) wykorzysta ten klucz do wykrycia duplikatów i zwróci ten sam wynik, zamiast wykonać operację ponownie.
Traktuj klucz idempotencji jak część modelu danych, a nie nagłówek, o którym masz nadzieję, że nikt nie zapomni.
Wygeneruj jeden klucz, gdy użytkownik zaczyna akcję (np. gdy klika Zapłać), a następnie zapisz go w lokalnym rekordzie.
Przy każdej próbie:
Jeśli jesteś "dostawcą" dla wewnętrznych wywołań, wymuszaj podobne zachowanie po stronie serwera.
Circuit breaker to przełącznik bezpieczeństwa. Gdy zewnętrzna usługa zaczyna się sypać, przestajesz ją wywoływać przez krótki okres, zamiast dorzucać kolejne żądania, które prawdopodobnie timeoutują.
Circuit breakery mają zwykle trzy stany:
Gdy breaker jest otwarty, aplikacja powinna robić coś przewidywalnego. Jeśli API walidacji adresu jest niedostępne przy rejestracji, zaakceptuj adres i oznacz go do późniejszego sprawdzenia. Jeśli kontrola ryzyka płatności jest niedostępna, odłóż zamówienie do ręcznej weryfikacji lub tymczasowo wyłącz tę opcję i wyjaśnij to użytkownikowi.
Wybierz progi dopasowane do wpływu na użytkownika:
Utrzymuj krótkie okresy chłodzenia (sekundy do minuty) i ogranicz liczbę testowych wywołań w stanie half-open. Celem jest najpierw ochronić podstawowe procesy, a potem szybko wrócić do normalnej pracy.
Gdy zewnętrzne API jest wolne lub niedostępne, twoim celem jest, aby użytkownik mógł kontynuować. To oznacza plan B, który szczerze informuje, co się stało.
Fallback to to, co robi aplikacja, gdy API nie odpowiada na czas. Opcje to korzystanie z danych z cache, przejście do trybu degradacji (ukryj elementy poboczne, wyłącz opcje opcjonalne), poproszenie użytkownika o dane zamiast wywołania API (ręczne wprowadzenie adresu) lub pokazanie jasnego komunikatu z kolejnym krokiem.
Bądź uczciwy: nie mów, że coś zostało wykonane, jeśli tak nie było.
Jeśli praca nie musi się zakończyć w żądaniu użytkownika, wrzuć ją do kolejki i odpowiedz szybko. Typowe kandydatury: wysyłanie e-maili, synchronizacja do CRM, generowanie raportów i wysyłanie zdarzeń analitycznych.
Dla kluczowych akcji zachowaj zasadę szybkiej porażki. Jeśli API nie jest wymagane do zakończenia checkoutu (lub tworzenia konta), nie blokuj żądania. Zaakceptuj zamówienie, umieść zewnętrzne wywołanie w kolejce i rozlicz się później. Jeśli API jest wymagane (np. autoryzacja płatności), zakończ szybko z jasnym komunikatem i nie każ użytkownikowi czekać.
To, co widzi użytkownik, powinno odpowiadać temu, co dzieje się za kulisami: jasny status (zakończone, oczekujące, niepowodzenie), obietnica, której możesz dotrzymać (paragon teraz, potwierdzenie później), możliwość ponowienia oraz widoczny zapis w UI (log aktywności, odznaka oczekujące).
Limity są sposobem dostawcy na powiedzenie: "Możesz nas wywoływać, ale nie za często." Trafisz w nie szybciej, niż myślisz: skoki ruchu, jednoczesne uruchomienie zadań w tle lub błąd, który wpada w pętlę na błędach.
Zacznij od kontroli, ile żądań generujesz. Grupuj, gdy to możliwe, cachuj odpowiedzi nawet na 30–60 sekund, gdy jest to bezpieczne, i ograniczaj przepustowość po stronie klienta, by twoja aplikacja nie robiła nagłych skoków większych niż pozwala dostawca.
Gdy dostaniesz 429 Too Many Requests, potraktuj to jako sygnał do zwolnienia tempa.
Retry-After, gdy jest podany.Ogranicz też współbieżność. Jeden proces (np. synchronizacja kontaktów) nie powinien zajmować wszystkich workerów i zabierać zasobów kluczowym przepływom jak logowanie czy checkout. Pomagają oddzielne pule pracowników lub limity per-funkcja.
Każde wywołanie do zewnętrznego API potrzebuje planu awaryjnego. Nie potrzebujesz perfekcji. Potrzebujesz przewidywalnego zachowania, gdy dostawca ma zły dzień.
Zdecyduj, co się stanie, jeśli wywołanie teraz zawiedzie. Kalkulacja podatku przy checkout może być krytyczna. Synchronizacja kontaktów marketingowych zwykle może poczekać. Ten wybór napędza resztę ustawień.
Dobierz timeouty dla typów wywołań i trzymaj się ich. Następnie ustal budżet retry, by nie bombardować wolnego API.
Jeśli żądanie może coś utworzyć lub obciążyć, dołącz klucze idempotencji i zapisuj rekordy żądań. Jeśli żądanie płatności timeoutuje, retry nie powinien podwójnie obciążyć. Śledzenie pomaga też wsparciu odpowiedzieć na pytanie: "Czy to przeszło?"
Gdy błędy rosną, przestań wywoływać dostawcę na krótki okres. Dla must-have pokaż jasną ścieżkę "spróbuj ponownie". Dla can-wait wrzuć zadanie do kolejki i przetwarzaj później.
Śledź latencję, wskaźnik błędów i zdarzenia otwarcia/zamknięcia breakerów. Alertuj na utrzymujące się zmiany, nie na pojedyncze skoki.
Większość przerw w działaniu nie zaczyna się wielka. Stają się wielkie, bo aplikacja reaguje w najgorszy możliwy sposób: czeka za długo, retryuje zbyt agresywnie i blokuje tych samych workerów, którzy utrzymują wszystko inne.
Te wzorce powodują kaskady:
Małe poprawki zapobiegają dużym awariom: retryuj tylko błędy, które prawdopodobnie są przejściowe (timeouty, niektóre 429, niektóre 5xx) i ogranicz liczbę prób z backoffem i jitter; utrzymuj timeouty krótkie i zamierzone; wymagaj idempotencji dla operacji tworzących lub obciążających; projektuj z myślą o częściowych awariach.
Przed wypchnięciem integracji do produkcji przejdź szybko z mindsetem awaryjnym. Jeśli nie możesz odpowiedzieć "tak" na pozycję, potraktuj to jako blokadę wydania dla kluczowych przepływów jak rejestracja, checkout czy wysyłka wiadomości.
Jeśli dostawca płatności zaczyna timeoutować, właściwe zachowanie to: "checkout nadal się ładuje, użytkownik dostaje jasny komunikat i nie wisimy w nieskończoność", a nie "wszystko stoi aż do timeoutu."
Wyobraź sobie checkout, który wywołuje trzy usługi: API płatności do pobrania środków, API podatkowe do obliczenia podatku i API e-mailowe do wysłania paragonu.
Wywołanie płatności jest jedynym, które musi być synchroniczne. Problemy z podatkami czy e-mailem nie powinny zablokować zakupu.
Powiedzmy, że API podatkowe czasem trwa 8–15 sekund. Jeśli checkout będzie czekał, użytkownicy porzucają koszyki, a aplikacja blokuje workerów.
Bezpieczniejszy przepływ:
Efekt: mniej porzuconych koszyków i mniej zablokowanych zamówień, gdy dostawca podatkowy jest wolny.
Wysłanie paragonu jest ważne, ale nigdy nie powinno blokować pobrania płatności. Jeśli API e-mailowe nie działa, circuit breaker powinien otworzyć się po kilku szybkich porażkach i zatrzymać kolejne wywołania na krótkie okno chłodzenia.
Zamiast wysyłać e-mail inline, umieść zadanie "wyślij paragon" w kolejce z kluczem idempotencji (np. order_id + email_type). Jeśli dostawca jest niedostępny, kolejka retryuje w tle, a klient nadal widzi udaną transakcję.
Efekt: mniej zgłoszeń do wsparcia z powodu brakujących potwierdzeń i brak utraty przychodów przez niepowodzenia niezwiązane z płatnościami.
Wybierz jeden przepływ, który najbardziej cierpi przy awarii (checkout, rejestracja, fakturowanie) i potraktuj go jako wzorcową integrację. Potem skopiuj te same domyślne ustawienia wszędzie.
Prosty porządek wdrożenia:
Zapisz swoje domyślne ustawienia i trzymaj je nudnymi: jeden connect timeout, jeden request timeout, maksymalna liczba retry, zakres backoffu, czas chłodzenia breaker i reguły, co można retryować.
Przeprowadź ćwiczenie awaryjne, zanim rozszerzysz to na kolejny przepływ. Wymuś timeouty (lub zablokuj dostawcę w środowisku testowym), a potem potwierdź, że użytkownik widzi użyteczny komunikat, fallbacky działają, a kolejki retry nie rosną bez końca.
Jeśli szybko tworzysz nowe produkty, warto zamienić te domyślne ustawienia niezawodności w wielokrotnego użytku szablon. Dla zespołów używających Koder.ai (koder.ai) często oznacza to zdefiniowanie timeoutu, retry, idempotencji i reguł breaker raz, a potem stosowanie tego samego wzorca w nowych usługach podczas generowania i iteracji.