Iza šablonskog koda: Arhitektura PHP-a za prilagodljivu otpornost pozadinskog sustava
Većina PHP pozadinskih sustava ne otkaže zato što kontroler sadrži jedan redak previše. Otkažu zato što uobičajeni kod potajno pretpostavlja da baze podataka odgovaraju, redovi prihvaćaju posao, API-ji trećih strana funkcioniraju i da se implementacije odvijaju bez prekida. Standardni predložak može učiniti aplikaciju urednom; otpornost je ono što je održava korisnom kada stvarnost postane neuredna.
Prilagodljiva otpornost nije obećanje da će svaki zahtjev uspjeti. To je sposobnost prepoznavanja promjenjivih uvjeta, ograničavanja štete i predvidljivog oporavka. U PHP-u to znači namjerno oblikovati granice: između HTTP-a i domenske logike, između sinkronog rada i pozadinskog rada te između lokalnog kvara i incidenta na razini cijelog sustava.
Počnite s kvarom kao ulaznim parametrom dizajna
Korisno pitanje za svaku ovisnost glasi: što bi ova značajka trebala učiniti kada je ovisnost spora, nedostupna ili vraća neispravne podatke? Odgovor bi trebao biti vidljiv u dizajnu, a ne zakopan u generičkom obrađivaču iznimki.
Stranica proizvoda i dalje se može prikazati kada preporuke nisu dostupne. Potvrda plaćanja ne smije se tretirati na isti način. Prva se može postupno degradirati; druga treba trajan tijek rada, jasan status i sigurno ponašanje pri ponavljanju. Otpornost započinje razlikovanjem neugodnosti od ispravnosti.
- Kritični tokovi štite ispravnost, čuvaju stanje i izlažu iskren ishod na čekanju ili neuspješan ishod.
- Tokovi obogaćivanja koriste zadane vrijednosti, predmemorirane podatke ili izostavljanje kada ovisnost zakaže.
- Pozadinski tokovi trajno prihvaćaju posao i obrađuju ga neovisno o izvornom zahtjevu.
Ova klasifikacija sprječava skupu pogrešku: pretvaranje svake integracije u sinkronu samo zato što ju je lakše pozvati iz kontrolera.
Neka HTTP sloj bude tanak i eksplicitan
Kontroleri bi trebali prevesti HTTP zahtjev u radnju aplikacije, a ne istodobno koordinirati upise u bazu podataka, udaljene pozive, petlje ponavljanja i odluke o formatiranju. Mala granica olakšava testiranje ponašanja i njegovu izmjenu kada vanjski sustav postane nepouzdan.
final class CreateOrderController
{
public function __invoke(CreateOrderRequest $request, CreateOrder $action): JsonResponse
{
$result = $action->handle(
customerId: $request->user()->id,
items: $request->validated('items'),
idempotencyKey: $request->header('Idempotency-Key')
);
return response()->json($result->toArray(), $result->httpStatus());
}
}
Radnja može upravljati poslovnom transakcijom i vratiti smislen ishod. Također može staviti naknadni posao u red nakon što je narudžba sigurno zabilježena. Kontroler ostaje dosadan, što je izvrsno svojstvo za kod na rubu sustava.
Učinite ponavljanja sigurnima prije nego što ih učinite čestima
Ponavljanja su vrijedna samo kada ponavljanje operacije ne može stvoriti dvostruke učinke. Mrežni kvarovi su dvosmisleni: klijentu može isteći vrijeme nakon što je udaljena usluga dovršila zahtjev. Slijepo ponavljanje može naplatiti dvaput, poslati dvije obavijesti ili stvoriti sukobljene zapise.
Za naredbe koje mijenjaju stanje prihvatite ključ idempotentnosti i uz njega pohranite dovršeni rezultat. Ako isti ključ stigne ponovno, vratite izvorni ishod umjesto da operaciju izvedete dvaput. Na razini baze podataka to dodatno ojačajte odgovarajućim jedinstvenim ograničenjem; same provjere u aplikaciji ranjive su na istodobne zahtjeve.
Ponavljanja također trebaju ograničenja. Koristite mali ograničeni broj pokušaja, povećavajuća kašnjenja i ukupno vremensko ograničenje koje odgovara korisničkom iskustvu. Ponavljajte prolazna stanja poput pogrešaka veze ili izričito ponovljivih odgovora poslužitelja. Ne ponavljajte pogreške provjere valjanosti, neuspjehe autorizacije ni neispravno oblikovane korisne podatke.
$attempts = 0;
while (true) {
try {
return $client->send($payload);
} catch (TransientTransportException $e) {
$attempts++;
if ($attempts >= 3) {
throw $e;
}
usleep(100_000 * $attempts);
}
}
Ovaj primjer prikazuje ograničenu logiku ponavljanja, a ne univerzalnu politiku. U produkciji je smjestite iza integracijskog klijenta kako bi vremenska ograničenja, pogreške koje se mogu ponoviti, zapisivanje i korelacijski podaci ostali dosljedni. Radnik bi također trebao imati vlastitu strategiju ponavljanja i neuspjelih poruka umjesto da nasljeđuje pretpostavke web zahtjeva.
Koristite redove za zaštitu latencije zahtjeva
Slanje e-pošte, generiranje izvješća, pozivanje web-dojava i osvježavanje izvedenih podataka obično su loši kandidati za ciklus zahtjev-odgovor. Red stvara granicu pritiska: web-proces može brzo potvrditi prihvaćeni posao, dok ga radnici troše kontroliranom brzinom.
Ta granica pomaže samo ako je poruka trajna i posao je oblikovan za ponavljanje. Posao se može izvršiti više puta nakon ponovnog pokretanja radnika ili neuspjeha potvrde primitka. Tretirajte ga kao isporuku barem jednom, osim ako konkretna infrastruktura pruža i dokumentira snažnija jamstva.
Gradite poslove oko vidljivog stanja
Umjesto posla koji samo kaže „pošalji račun”, trajno pohranite stanje računa i dopustite poslu da ga sigurno unaprijedi. Zabilježite pokušaj, vanjsku referencu gdje je dostupna i razlog neuspjeha prikladan za operatere. Baza podataka postaje izvor istine; red postaje mehanizam isporuke posla.
Time se i oporavak čini praktičnim. Operater može ponoviti poznato neuspješno stanje, dok periodični postupak usklađivanja može prepoznati zapise koji su ostali na čekanju dulje od očekivanog.
Neka baza podataka provodi invarijante
PHP kod izražava namjeru, ali baza podataka mora zaštititi podatke kada više procesa djeluje istodobno. Koristite transakcije za promjene koje moraju uspjeti zajedno, strane ključeve tamo gdje odgovaraju modelu i jedinstvena ograničenja za identitete i uklanjanje duplikata. Transakcije neka budu kratke: nemojte držati zaključavanja baze podataka dok čekate udaljeni API.
Pouzdan obrazac je prvo potvrditi lokalnu promjenu, a zatim objaviti događaj za vanjski rad. Kada bi gubitak tog događaja bio neprihvatljiv, zapišite outbox zapis u istoj transakciji kao i poslovnu promjenu. Radnik kasnije može objaviti neriješene outbox zapise i označiti ih isporučenima. Time se izbjegava krhki jaz između „narudžba je potvrđena” i „događaj je poslan”.
Oblikujte kontejnere za zamjenu, a ne popravak
Docker podržava otpornost kada su kontejneri zamjenjivi. Konfiguracija bi trebala stizati kroz konfiguraciju specifičnu za okruženje, zapisnici bi trebali ići na standardni izlaz i standardnu pogrešku, a stanje aplikacije trebalo bi živjeti u upravljanim uslugama ili eksplicitnoj trajnoj pohrani. Izbjegavajte ovisnost o lokalnom datotečnom sustavu kontejnera za sesije, prijenose ili poslove u redu osim ako je ta trajnost namjerna i podržana dizajnom implementacije.
Provjere zdravlja trebale bi odražavati ono što tvrde. Lagana provjera živosti može potvrditi da PHP proces odgovara. Provjera spremnosti trebala bi odgovoriti može li instanca sigurno posluživati promet, bez pretvaranja svakog ispitivanja u skupu potpunu reviziju ovisnosti. Ako je nizvodna usluga preporuka opcionalna, njezin prekid ne mora nužno učiniti cijelu aplikaciju nespremnom.
Mjerite signale koji usmjeravaju odluke
Zapisivanje svake iznimke nije opažljivost. Korisni operativni signali povezuju zahtjev, posao i vanjski poziv putem korelacijskog identifikatora. Razdvajaju očekivana poslovna odbijanja od pogrešaka sustava, bilježe trajanja i ishode ovisnosti te izbjegavaju otkrivanje vjerodajnica ili osobnih podataka.
- Pratite latenciju zahtjeva, stopu pogrešaka, dubinu reda, starost posla i broj ponavljanja.
- Bilježite vremenska ograničenja ovisnosti odvojeno od neuspjeha aplikacije.
- Upozoravajte na trajne simptome, a ne na jedan bučan događaj.
- Uključite dovoljno strukturiranog konteksta za prepoznavanje operacije bez otkrivanja osjetljivih korisnih podataka.
Cilj nije nadzorna ploča puna grafikona. Cilj su brže, smirenije odluke kada se ponašanje promijeni pod opterećenjem ili tijekom prekida rada.
Otpornost je skup namjernih kompromisa
Ne postoji jedinstvena „otporna arhitektura” koju možete kopirati u PHP projekt. Malom internom alatu možda trebaju jednostavna transakcija i jasna poruka o pogrešci. Tijek rada okrenut korisnicima može opravdati idempotentnost, outbox, redove i usklađivanje. Zreo izbor je proporcionalna složenost: izgradite zaštitne mjere ondje gdje kvar ima značajne posljedice, a sve ostalo zadržite razumljivim.
Iza standardnog predloška, snažna arhitektura pozadinskog sustava manje se odnosi na domišljate apstrakcije, a više na iskrene granice. Imenujte kvarove koje očekujete, odlučite što bi korisnici trebali doživjeti, učinite ponovljeni rad sigurnim i ostavite tragove koji sljedećoj osobi pomažu dijagnosticirati sustav. Kada se uvjeti promijene, ta disciplina PHP aplikacijama daje prostora da se saviju bez pucanja.