Iza okvira: Projektiranje otpornosti API-ja od samih temelja
Većinu API kvarova ne uzrokuje nedostatak značajke okvira. Nastaju zbog pretpostavki: da će ovisnosti odgovoriti, klijenti se ponašati ispravno, ponovni pokušaji pomoći, baze podataka ostati dostupne i implementacije proći bez poteškoća. Okvir može učiniti krajnju točku ugodnom za izradu. Otpornost zahtijeva odluke koje sežu daleko izvan kontrolera.
Za PHP backend timove ta je razlika važna. Jasna definicija rute i uredan servisni spremnik korisni su, ali ne odgovaraju na teža pitanja: što se događa kada pružatelj usluge plaćanja istekne nakon obrade zahtjeva? Što se događa kada klijent ponovi zahtjev za zapisivanje? Što se događa kada se skup veza s bazom podataka iscrpi usred naglog porasta prometa?
Otporni API-ji od početka su dizajnirani oko takvih neugodnih putanja.
Počnite s granicama neuspjeha, a ne krajnjim točkama
Prije odabira međuprograma ili pisanja pravila validacije, mapirajte ovisnosti iza svake operacije. Krajnja točka koja čita jedan indeksirani redak baze podataka ima vrlo drukčiji profil rizika od one koja zapisuje u više tablica, emitira događaj, poziva vanjsku uslugu i vraća potvrdu.
Za svaku ovisnost definirajte tri stvari: vremensko ograničenje, ponašanje pri zamjeni i odgovornost za oporavak. Zahtjev ne bi trebao čekati neograničeno zato što je uzvodna usluga spora. Također, svaki sloj ne bi trebao samostalno pokušavati ponovno; naslagani ponovni pokušaji mogu kratko usporavanje pretvoriti u samonametnuti prekid rada.
Praktično je pravilo svakom zahtjevu dodijeliti vremenski proračun. Ako klijent može čekati pet sekundi, namjerno podijelite taj proračun između rada s bazom podataka, nizvodnih poziva i male rezerve za serijalizaciju i mrežnu latenciju. Kada ovisnost ne može završiti unutar svojeg dijela, neuspjeh mora biti predvidljiv.
$response = $httpClient->request('POST', $url, [
'timeout' => 2.0,
'json' => $payload,
]);
if ($response->getStatusCode() >= 500) {
throw new UpstreamUnavailableException();
}
Nije bitna točna biblioteka klijenta. Važno je da postoji vremensko ograničenje i da aplikacija neuspjeh ovisnosti prevodi u smislen ishod. Klijent može primiti pogrešku poslužitelja za koju je moguće ponoviti pokušaj, odgovor da je zahtjev stavljen u red čekanja ili jasno odbijanje. Ne bi smio primiti slučajnu generičku pogrešku nakon dugog, neobjašnjenog čekanja.
Učinite operacije zapisivanja sigurnima za ponavljanje
Mreže su dovoljno nepouzdane da će klijenti ponavljati pokušaje. Mobilne veze se prekidaju. Uravnoteživači opterećenja mogu izgubiti odgovor nakon što je aplikacija dovršila posao. Korisnik može dvaput kliknuti gumb. Ako POST krajnja točka stvara narudžbu, naplaćuje karticu ili rezervira zalihe, “klijent ne bi trebao ponoviti pokušaj” nije strategija otpornosti.
Upotrebljavajte idempotentnost za javno vidljive operacije zapisivanja. Klijent šalje jedinstveni ključ idempotentnosti, a poslužitelj bilježi i ključ i konačni rezultat. Ponovljeni zahtjev s istim ključem vraća izvorni ishod umjesto da ponovno izvrši radnju.
Zapis treba stvoriti atomski s poslovnom operacijom gdje je to moguće. U suprotnom, dva istodobna zahtjeva mogu oba utvrditi da ključ ne postoji i svaki izvršiti zapisivanje. Ograničenje jedinstvenosti u bazi podataka pouzdanije je od same provjere postojanja na razini aplikacije.
CREATE TABLE idempotency_keys (
key_value VARCHAR(255) PRIMARY KEY,
response_status INTEGER NOT NULL,
response_body TEXT NOT NULL,
created_at TIMESTAMP NOT NULL
);
Idempotentnost također nameće korisne odluke o proizvodu. Koliko dugo treba čuvati ključeve? Što bi se trebalo dogoditi ako isti ključ stigne s drukčijim sadržajem? Obično je sigurnije odbiti takvo nepodudaranje nego tiho vratiti rezultat za nepovezani zahtjev.
Upotrebljavajte bazu podataka kao alat za dosljednost
Backend kod treba izražavati poslovna pravila, ali baza podataka treba provoditi invarijante koje se nikada ne smiju prekršiti. Ograničenja jedinstvenosti, strani ključevi, ograničenja provjere gdje je primjereno i transakcije nisu ostaci starije arhitekture. Oni su posljednja pouzdana linija obrane kada se istodobni zahtjevi ili pozadinski radnici sukobe.
Česta je zamka koordinirati zapisivanje u bazu podataka i objavu događaja u jednom zahtjevu. Ako se transakcija baze podataka potvrdi, ali objava ne uspije, sustav se promijenio bez obavještavanja nizvodnih potrošača. Ako objava uspije, ali se transakcija poništi, potrošači saznaju za nešto što se nikada nije dogodilo.
Obrazac izlaznog spremnika rješava to pohranjivanjem poslovne promjene i neobjavljenog događaja u istoj transakciji. Zaseban radnik čita i objavljuje zapise izlaznog spremnika na čekanju. Taj radnik mora tolerirati dvostruku isporuku jer rušenje nakon objave, ali prije označavanja događaja dovršenim, može uzrokovati ponovno slanje. Stoga i potrošači trebaju idempotentno rukovanje.
To nije nepotrebna ceremonija. To je izričit izbor za očuvanje ispravnosti kada rad prelazi granice procesa ili usluga.
Odvojite sinkrona obećanja od pozadinskog rada
API zahtjev trebao bi dovršiti samo rad potreban da pozivatelju pruži istinit odgovor. Slanje e-pošte, generiranje izvoza, ponovno izračunavanje indeksa pretraživanja i obavještavanje više vanjskih sustava često je bolje obraditi asinkrono.
Redovi čekanja poboljšavaju odaziv, ali ne uklanjaju neuspjeh. Poslovi trebaju ograničene ponovne pokušaje, korisno izvještavanje o pogreškama i odredište za rad koji ne može uspjeti ni nakon razumnih pokušaja. Politika ponovnih pokušaja trebala bi razlikovati prolazna stanja od trajnih. Ponovno pokušavanje nedostupne usluge može biti smisleno; ponovno pokušavanje neispravnog unosa obično je rasipno.
- Uključite stabilan identifikator posla u zapisnike i odgovore o pogreškama gdje je to primjereno.
- Postavite najveći broj pokušaja i namjerno odredite odgodu između pokušaja.
- Osigurajte da radnici mogu sigurno obraditi posao više puta.
- Pratite starost reda čekanja, a ne samo njegovu duljinu; stari rad često je značajniji signal.
- Osigurajte operativni put za pregled i ponovno izvršavanje neuspjelih poslova.
Dizajnirajte pogreške kao dio ugovora
Klijenti ne mogu izgraditi pouzdano ponašanje oko nejasnih neuspjeha. Definirajte dosljedan oblik pogreške, pažljivo upotrebljavajte HTTP statusne kodove i uključite stabilan strojno čitljiv kôd pogreške. Poruka može pomoći čovjeku, ali kôd je ono na temelju čega bi klijent trebao djelovati.
{
"error": {
"code": "inventory_unavailable",
"message": "The requested quantity is not currently available."
}
}
Ne izlažite tragove stoga, SQL naredbe, vjerodajnice ni internu topologiju. Te pojedinosti interno bilježite s identifikatorom zahtjeva ili korelacije. Isti identifikator može se vratiti pozivatelju kao referenca za podršku bez pretvaranja interne dijagnostike u značajku API-ja.
Učinite implementaciju pitanjem otpornosti
Mnogi kvarovi u produkciji uvode se tijekom inače rutinskih izdanja. Promjene sheme i izdanja aplikacije moraju biti kompatibilni tijekom prijelaza, osobito kada više instanci aplikacije može kratko pokretati različite verzije.
Dajte prednost migracijama proširi-pa-sažmi: najprije dodajte stupac koji dopušta NULL ili novu tablicu, implementirajte kod koji može raditi s oba oblika, po potrebi popunite podatke, a zatim uklonite stari put u kasnijem izdanju. Izbjegavajte implementaciju koda koji pretpostavlja da je migracija završena dok stare instance još ovise o prethodnoj shemi.
Docker pomaže dosljedno pakirati aplikaciju, ali spremnik nije automatski zdrav samo zato što je njegov proces pokrenut. Spremnost bi trebala odražavati može li aplikacija sigurno prihvatiti promet. Živost bi trebala odgovoriti na uže pitanje: treba li proces zamijeniti. Miješanje ta dva pojma može uzrokovati da se zdrave instance koje čekaju ponovno pokreću tijekom prekida rada uzvodne usluge.
Otpornost je arhitektonska navika
Najjači API-ji nisu oni koji obećavaju da nikada neće zakazati. To su oni koji zakazuju unutar poznatih granica, čuvaju cjelovitost podataka, jasno komuniciraju i oporavljaju se bez potrebe za herojskim naporima.
Okviri ostaju vrijedni ubrzivači, ali nisu arhitektura. Arhitektura se nalazi u vremenskim ograničenjima, transakcijama, ključevima idempotentnosti, semantici redova čekanja, strategiji migracije, promatranju sustava i odlukama koje se donose kada ne ide sve prema planu. Ugradite te odluke rano u sustav i API će biti lakše mijenjati upravo zato što ga je teže pokvariti.