Un furnizor de hoteluri nu a răspuns. De ce s-a oprit toată platforma de rezervări?
Trei furnizori de hoteluri primesc aceeași căutare. Doi răspund normal. Al treilea rămâne blocat. După câteva secunde, călătorul vede o eroare, nu rezultatele de la furnizorii care funcționau.
Nu e doar o problemă a unui API din amonte. E o problemă de integrare cu API-ul furnizorului de hoteluri: o dependență a putut decide rezultatul întregului flux.
O căutare eșuată e neplăcută. O rezervare eșuată e mai gravă. Când cererea de rezervare dă timeout, rezervarea poate exista deja, chiar dacă platforma n-a primit confirmarea. Ce urmează decide dacă clientul primește un răspuns corect sau o a doua rezervare.
Ținta utilă nu e „să nu apară niciodată o eroare de API”. E să izolezi eșecurile și să păstrezi starea corectă a afacerii când informația e incompletă.
Eșecul la căutare și eșecul la rezervare nu sunt aceeași problemă
O căutare de hotel e o citire. În funcție de produs și de ce i s-a promis clientului, platforma poate rămâne utilă și când un furnizor nu răspunde. O cerere de rezervare schimbă lumea din afară: o cameră poate fi reținută, iar o obligație de plată poate exista deja.
Operațiile astea au nevoie de politici diferite.
| Situație | Comportament potrivit |
|---|---|
| SituațieUnul din trei furnizori de căutare dă timeout | Comportament potrivitÎntoarce rezultatele valide de la ceilalți, dacă regulile o permit, și marchează acoperirea incompletă |
| SituațieToți furnizorii de căutare eșuează | Comportament potrivitSpune că verificarea disponibilității nu s-a putut termina. Nu spune că hotelurile sunt sold out |
| SituațieUn token de tarif de la căutare expiră | Comportament potrivitReîmprospătează disponibilitatea sau detaliile tarifului, în fluxul acelui furnizor |
| SituațieRăspunsul la rezervare dă timeout | Comportament potrivitMarchează starea ca incertă. Recuperează sau reconciliază încercarea anterioară înainte de un retry |
| SituațieRezervarea e respinsă în mod sigur | Comportament potrivitArată eșecul confirmat, cu opțiuni sigure de recuperare |
Dacă tratezi orice eroare de API ca „nu e disponibilitate”, ascunzi o pană a furnizorului de operațiuni și poți induce călătorul în eroare.
Nu lăsa un furnizor lent să consume toată platforma
Un agregator obișnuit împrăștie o cerere către mai multe sisteme externe. Fără izolare, un furnizor lent poate ocupa thread-uri, pool-uri de conexiuni sau cozi comune, până când și furnizorii sănătoși încep să cedeze.
Un design mai robust pune un termen, o limită de concurență și o urmărire a sănătății pentru fiecare furnizor. Serviciul poate aduna rezultatele care s-au terminat în bugetul de timp și poate spune care furnizori n-au terminat.
Pattern-ul bulkhead de la Microsoft descrie pool-uri de resurse izolate, ca eșecul unei componente să nu epuizeze resursele de care au nevoie celelalte. Pattern-ul circuit breaker descrie blocarea apelurilor care probabil vor eșua din nou, pentru o vreme, lăsând totuși o verificare ulterioară că dependența și-a revenit. Pattern-urile rezolvă probleme diferite și ambele cer reglaj atent. Un circuit breaker protejează apelantul. Nu face o cerere de rezervare idempotentă.
Un flux ilustrativ:
- Furnizorul A răspunde în fereastra de căutare: include ofertele lui valide.
- Furnizorul B răspunde normal: include ofertele lui valide.
- Furnizorul C depășește termenul: înregistrează supplier_unavailable, nu no_rooms.
- Stratul de agregare întoarce rezultatele disponibile, cu o notă internă clară despre ce s-a terminat și ce acoperire lipsește.
Dacă clientul trebuie să vadă rezultate parțiale ține de ce promite produsul de rezervări. E o decizie de business, nu un accident al apelului HTTP care a terminat primul.
Arhitectură ilustrativă
Un furnizor picat, căutarea încă merge
Aceeași cerere de căutareTrimisă în paralel. Fiecare furnizor are propriul termen și propria limită de concurență.
- Furnizorul ALimită proprie
Răspunde în fereastră. Ofertele valide sunt incluse.
- Furnizorul BLimită proprie
Răspunde normal. Ofertele valide sunt incluse.
- Furnizorul CLimită proprie, depășită
Timeout. Înregistrat ca supplier_unavailable, nu ca no_rooms.
Oferte de la A și B, cu acoperirea marcată incompletă pentru că C n-a răspuns.
Trei apeluri către furnizori rulează în paralel, fiecare în limita lui. Două întorc oferte. Unul dă timeout. Căutarea tot întoarce rezultatele reușite și spune că acoperirea e incompletă.
O strategie de retry la API-ul de rezervare nu poate fi „încearcă din nou”
Documentația de erori Expedia Rapid avertizează că un timeout la rezervare sau un răspuns 5xx nu invalidează neapărat o rezervare. Eșecul poate apărea după ce rezervarea a fost creată. Expedia documentează recuperarea cu referința originală de rezervare și identificatorii legați, ca starea finală să poată fi stabilită. Comportamentul ăsta e al Expedia, descris în documentația Rapid.
Imaginează-ți secvența asta, tot ilustrativă:
- Aplicația trimite o cerere de rezervare cu un identificator de încercare care poate fi urmărit.
- Furnizorul creează rezervarea.
- Răspunsul HTTP se pierde.
- Interfața raportează o eroare de rețea.
- Clientul apasă din nou Book.
Fără o cale de recuperare controlată, pasul al cincilea poate repeta o tranzacție reală. Dacă o cerere repetată e sigură ține de contractul de idempotență documentat de furnizor și de faptul că furnizorul poate întoarce starea. Nu ține de un UUID generat în backend. Un identificator generat local nu e o garanție de idempotență pe partea furnizorului.
Un timeout nu dovedește că rezervarea a eșuat. Prima acțiune corectă după o rezervare incertă e, de obicei, să rezolvi încercarea originală prin recuperarea sau reconcilierea pe care o suportă furnizorul, nu să trimiți aceeași mutație încă o dată.
Arhitectură ilustrativă
Rezervarea există, dar răspunsul s-a pierdut
- Trimite cererea
Aplicația trimite o încercare de rezervare cu un identificator care poate fi urmărit.
- Furnizorul creează rezervarea
Camera e reținută la furnizor. Evenimentul s-a întâmplat deja.
- Răspunsul dă timeout
Confirmarea HTTP nu ajunge. Rezervarea și răspunsul sunt evenimente separate.
- Recuperează rezervarea existentă
Caută încercarea originală prin procesul de retrieval sau reconciliere al furnizorului.
- Confirmă rezultatul
Starea devine cunoscută din căutarea aia, nu dintr-o a doua mutație de rezervare.
Furnizorul poate crea rezervarea și tot să nu întoarcă răspunsul HTTP. Recuperarea aduce rezervarea care există deja și îi confirmă rezultatul. Nu trimite mutația din nou pe ideea că timeout-ul a însemnat eșec.
Fă starea tranzacției explicită
Merită să persiști o încercare de rezervare înainte să trimiți cererea externă. O mașină de stări simplă, la nivelul aplicației, poate include:
requested → submitted → pending_resolution → confirmed | failed
Unele aplicații au nevoie și de cancellation-pending, amendment-pending sau manual-review. Sunt stări de proiectare sugerate. Nu e afirmația că fiecare furnizor expune același model de status.
Starea pending_resolution e utilă. Spune și software-ului, și echipei de suport, că rezultatul e necunoscut, în loc să înregistreze încercarea ca o cumpărare respinsă.
Un proces sigur leagă retry-urile și recuperarea de identitatea originală, verifică dacă există deja o confirmare, notează ce a aprobat clientul și ține plata și datele călătorului în afara logurilor.
Ghidul AWS despre API-uri idempotente explică de ce contează un identificator de cerere recunoscut și tratarea atomică a unei mutații duplicate. Proprietățile astea trebuie să stea în contractul API-ului, sau într-o graniță de tranzacție pe care o controlezi. Nu pot fi presupuse pentru fiecare furnizor de hoteluri.
Normalizează datele furnizorului fără să ascunzi diferențele
Un model canonic de rezervare simplifică platforma. Normalizarea nu e permisiunea de a arunca diferențele utile.
Un adapter poate converti câmpurile de cameră, masă și anulare într-o formă comună. Tot ar trebui să păstreze identificatorii originali, textul politicii, codurile de diagnostic și referințele de corelare, ca o rezervare contestată să poată fi urmărită până la sursă.
Un API poate întoarce o eroare de business în JSON structurat. Alt strat poate întoarce o pagină HTML de la un gateway. Referința de erori Expedia notează că pot apărea formate neașteptate. Un parser care presupune că orice eroare e JSON cu un câmp message poate cădea exact când platforma are cea mai mare nevoie să cedeze blând.
Validează formatul răspunsului defensiv, clasifică ce nu recunoști și păstrează destulă proveniență de diagnostic ca să poți investiga. Redactează plata și datele călătorului înainte să ajungă într-un log.
Monitorizează incertitudinea de business, nu doar uptime-ul HTTP
Un dashboard de infrastructură verde nu înseamnă că clienții au rezervări confirmate. Măsurile utile includ:
- Căutări terminate, pe furnizor și pe operație.
- Latența răspunsului și rata de timeout în bugetul de timp al utilizatorului.
- Eșecuri de tarif și de parsare, pe furnizor și pe plan tarifar.
- Încercări de rezervare care stau nerezolvate, și cât de vechi sunt.
- Succesul reconcilierii, și timpul până la rezolvare.
- Evenimente de detectare a duplicatelor și escaladări către suport.
Pentru citiri care pot fi reîncercate în siguranță, folosește un număr limitat de retry-uri, cu backoff și jitter unde se potrivește. AWS Well-Architected spune să controlezi traficul de retry. Retry-uri fără limită pot înrăutăți un incident. Același obicei de „încearcă din nou” nu își are locul pe o mutație de rezervare.
Reziliența e ce face sistemul după ce drumul fericit se rupe
O platformă de rezervări nu devine fiabilă dacă presupui că fiecare furnizor e fiabil. Devine fiabilă izolând dependențele, păstrând ce înseamnă o ofertă, deosebind o stare incertă de un eșec confirmat și dându-le operatorilor un drum clar de recuperare.
Dacă o pană a unui singur furnizor doboară regulat căutarea, sau dacă un timeout la rezervare îi face pe oameni să reîncerce o mutație de mână, revizuiește izolarea eșecurilor și recuperarea stării rezervării înainte să adaugi încă o integrare.
WebbingHUB Travel lucrează în conectivitatea cu furnizorii și în fluxurile de rezervare. Începutul util e o excepție reală pe care echipa n-o poate rezolva.
Surse și lecturi
- Expedia Group — Rapid API common error responsesComportament de timeout și retrieval specific furnizorului. Un timeout sau un 5xx nu dovedește că rezervarea n-a fost creată.
- Microsoft Azure Architecture Center — Bulkhead patternIzolarea resurselor, ca eșecul unei componente să nu epuizeze pool-urile folosite de celelalte.
- Microsoft Azure Architecture Center — Circuit Breaker patternProtejarea apelurilor la distanță în timpul unui eșec repetat. Nu creează idempotență.
- AWS Builders' Library — Making retries safe with idempotent APIsIdentitatea tranzacției și deduplicarea. Proprietățile astea trebuie să fie în contractul API-ului sau într-o graniță pe care o controlezi.
- AWS Well-Architected — Control and limit retry callsPolitici de retry cu limită. Retry-urile fără limită pot înrăutăți un incident.