Łamigłówka: operacja bez odpowiedzi
W fikcyjnym systemie rozproszonym usługa A zleca usłudze B operację zmieniającą stan biznesowy. Odpowiedź nie dociera przed upływem limitu czasu. B mogła już zatwierdzić zmianę, nadal ją wykonywać albo w ogóle nie otrzymać żądania.
A zapisuje UNKNOWN. To nie musi być stan operacji w B; jest to stan wiedzy usługi A. Jeśli A wyśle nową komendę, może podwoić skutek. Jeśli uzna timeout za błąd biznesowy, również może się pomylić.
Twoje zadanie: zaprojektuj system, który wykrywa niejednoznaczne wyniki, ustala rezultat tam, gdzie to możliwe, i kieruje przypadki nierozstrzygnięte do kontrolowanej eskalacji — także wtedy, gdy użytkownik nigdy nie zgłosi problemu.
Ten scenariusz jest całkowicie fikcyjny i nie opisuje rzeczywistego przedsiębiorstwa.
Trzy podejścia
A. Odpytywanie o status
A nadaje stabilny identyfikator operacji i odpytuje B o jej wynik. B oferuje status API, które rozróżnia stany przejściowe i terminalne.
Plusy: stosunkowo prosta implementacja, czytelna odpowiedzialność, łatwe audytowanie. Minusy: ruch odpytywania, zależność od dostępności B, limitów zapytań i opóźnionych aktualizacji. Samo HTTP 404 nie powinno oznaczać automatycznie, że operacja nie została wykonana, jeśli kontrakt takiej gwarancji nie zapewnia.
B. Zdarzenia i rekoncyliacja
B po trwałym zatwierdzeniu wyniku publikuje zdarzenie, a A aktualizuje swój widok. Spójność zapisu wyniku i zamiaru publikacji wymaga osobnego zabezpieczenia, np. transactional outbox. Osobny proces cyklicznie wykrywa operacje bez potwierdzenia, uzgadnia stany i odnajduje wiadomości utracone lub opóźnione.
Plusy: mniejsza zależność od synchronicznej odpowiedzi, lepsza skalowalność przy większej liczbie operacji. Minusy: dodatkowe komponenty, duplikaty i kolejność komunikatów, konieczność mechanizmu deduplikacji oraz trwałego zapisu zdarzeń. Sam event nie zastępuje źródła prawdy.
C. Kolejka wyjątków i odpowiedzialność operacyjna
Po ograniczonej liczbie prób nierozstrzygnięte przypadki trafiają do obsługi operacyjnej. Każdy otrzymuje właściciela, termin, historię działań i ścieżkę eskalacji.
Plusy: skrajne przypadki nie znikają. Minusy: czas i koszt ręcznej obsługi; bez technicznego mechanizmu odczytu stanu pracownik nadal nie będzie wiedział, co zaszło. Reklamacja użytkownika nie może stanowić jedynego wyzwalacza procesu.
Pułapka w pytaniu
Nie musisz wybrać dokładnie jednego wariantu. W praktyce uzasadnione bywa połączenie stabilnego identyfikatora i idempotentnego wykonania z odpytywaniem lub zdarzeniami, okresową rekoncyliacją i eskalacją wyjątków.
Nie obiecuj uniwersalnego „exactly once” pomiędzy niezależnymi komponentami. Zapewnij raczej brak powtórzenia efektu biznesowego w systemie realizującym: ten sam klucz idempotencji musi oznaczać tę samą intencję, a historia kluczy musi być przechowywana wystarczająco długo. Identyczny zestaw pól może czasem oznaczać dwie różne, zamierzone operacje.
Pamiętaj: jeśli zapis idempotency key i skutku biznesowego odbywa się we wspólnym magazynie transakcyjnym, powinien być atomowy. Przy odrębnych zasobach potrzebny jest jawny mechanizm przywracania spójności po częściowej awarii.
Kontrakt architektoniczny
- Źródło prawdy: B lub wskazany rejestr jest autorytatywnym źródłem wyniku. UNKNOWN w A oznacza niepewność, a nie brak realizacji.
- Identyfikacja: trwały identyfikator operacji, klucz idempotencji i correlation ID; ponowienie nie może tworzyć nowej intencji.
- Model stanów: np. RECEIVED, PROCESSING, SUCCEEDED, REJECTED, plus UNKNOWN jako stan wiedzy inicjatora. Przejścia i znaczenie stanów są zapisane w kontrakcie.
- Czas i odpowiedzialność: osobno określony timeout odpowiedzi oraz docelowy czas ostatecznego rozstrzygnięcia, właściciel procesu i zasady eskalacji.
- Wyszukiwanie braków: cykliczna identyfikacja rekordów bez końcowego potwierdzenia — również bez zgłoszenia użytkownika.
- Ślad audytowy: kiedy zlecono, co potwierdzono, kto lub co rozstrzygnęło i na jakiej podstawie.
Antywzorzec: „timeout to błąd, resztą zajmie się reklamacja”
Timeout komunikacyjny nie oznacza odrzucenia operacji. Proces reklamacyjny jest potrzebny do obsługi użytkownika, ale nie zapewnia spójności danych i nie wyszukuje sam z siebie niezgłoszonych rozbieżności.
Nie należy również uruchamiać kompensacji wyłącznie na podstawie braku odpowiedzi. Najpierw trzeba ustalić, jakie skutki rzeczywiście zaszły oraz czy ich odwrócenie jest dozwolone.
Mini-ADR: przykładowa decyzja
Decyzja: B wykonuje komendy idempotentnie i utrzymuje autorytatywny rejestr wyniku. A przechowuje własny rejestr oczekujących operacji. Dostępne są status API i okresowy proces rekoncyliacji; zdarzenia mogą przyspieszać aktualizację. Przypadki przekraczające ustalony czas trafiają do kontrolowanej kolejki wyjątków.
Dlaczego: ważniejsze są weryfikowalny końcowy rezultat i wykluczenie podwójnych skutków niż pozorne natychmiastowe potwierdzenie.
Koszty: magazyn stanu idempotencji, API, cykliczne zadania, monitoring i obsługa wyjątków. Skala rozwiązania zależy od ryzyka i wolumenu.
Testy akceptacyjne: utrata odpowiedzi po zatwierdzeniu; kilkukrotne ponowienie komendy; duplikaty i opóźnienia zdarzeń; brak potwierdzenia; niedostępność status API; UNKNOWN bez reklamacji; wykrycie i eskalacja w terminie.
Twoja decyzja
Jak zaprojektujesz rozwiązanie minimalne, a jak rozwiązanie dla systemu krytycznego? Kto odpowiada za wynik, kto za rekoncyliację, a kto za wyjątek?
Źródła wzorców architektonicznych
- AWS Builders' Library — Making retries safe with idempotent APIs.
- Microsoft Learn — Asynchronous Request-Reply.
- Microsoft Learn — Compensating Transaction.
Źródła opisują wzorce techniczne, a nie przedstawiony fikcyjny przypadek. Scenariusz i mini-ADR są oryginalnym opracowaniem.
Sprawdź swoją decyzję w praktyce
Ten materiał ma również interaktywną łamigłówkę: trzy odpowiedzi w dwie minuty albo warsztat z mentorem AI.
Porozmawiajmy o tym artykule
Podziel się uwagą lub zanonimizowanym przykładem. Komentarze sprawdza AI przed publikacją. Niejednoznaczne przypadki trafiają do moderatora.