Iza skupa tehnologija: projektiranje otpornih sustava za promjenjive zahtjeve
Većina sustava ne propada zato što je tim odabrao pogrešan programski jezik, bazu podataka ili pružatelja usluga u oblaku. Propadaju zato što se jučerašnje razumne pretpostavke neprimjetno pretvore u današnja ograničenja. Jedan korisnik postane mnogi. Sinkroni zahtjev počne koordinirati rad među servisima. Upit baze podataka koji je pri pokretanju bio bezopasan počne se natjecati sa svakim drugim zahtjevom.
Otpornost se stoga manje odnosi na izgradnju impresivnog skupa tehnologija, a više na projektiranje za promjene. U PHP pozadinskom razvoju to znači jasno definirati granice, održavati operativno ponašanje vidljivim i uvoditi složenost samo kada rješava stvaran pritisak.
Počnite s odgovornostima, a ne tehnologijama
Servis bi trebao biti razumljiv prema onome za što je odgovoran. „API aplikacija” obično je preširoka odgovornost. Jasniji opis mogao bi biti: validira zahtjeve korisnika, primjenjuje poslovna pravila, pohranjuje mjerodavno stanje svoje domene i objavljuje ishod za druge dijelove sustava.
Ovakvo uokvirivanje poboljšava odluke prije nego što u raspravu uđu bilo koji okvir ili dijagram infrastrukture. Otkriva pitanja koja su važna: Koja je komponenta izvor istine? Koje operacije moraju završiti prije nego što korisnik primi odgovor? Koji se rad može sigurno obaviti kasnije? Tko smije izmijeniti zapis?
Modularni monolit često je izvrstan prvi odgovor. Može zadržati povezani kod, transakcije i pitanja implementacije jednostavnima, uz provođenje granica putem modula, sučelja i testova. Podjela na servise opravdana je kada neovisno skaliranje, vlasništvo, ritam implementacije ili izolacija kvarova stvaraju konkretnu korist — ne samo zato što arhitekturni dijagram izgleda modernije.
Dizajnirajte API-je oko trajnih ugovora
API je obećanje dano u nesavršenim uvjetima. Klijenti se sporo nadograđuju, mreže ponovno pokušavaju zahtjeve, a neispravan unos na kraju stigne. Otporan API tretira to kao normalne uvjete rada, a ne kao iznimne rubne slučajeve.
Koristite nazive orijentirane na resurse gdje odgovaraju, ali dajte prednost dosljednom ponašanju pred stilskom čistoćom. Odgovori na pogreške trebaju imati stabilan oblik, neuspjesi validacije trebaju navesti pogođena polja, a statusni kodovi trebaju točno odražavati ishod. Klijent ne bi trebao morati raščlanjivati ljudsku rečenicu kako bi odlučio može li ponovno pokušati.
Ponovnim pokušajima treba posvetiti posebnu pažnju. Ako klijent nakon isteka vremena ponovi zahtjev za plaćanje, narudžbu ili dodjelu resursa, poslužitelj ne smije slijepo dvaput izvršiti radnju. Za operacije sa značajnim nuspojavama prihvatite ključ idempotentnosti i pohranite rezultat povezan s tim ključem. Ponovljeni zahtjev tada može vratiti izvorni ishod umjesto stvaranja novog.
public function createOrder(Request $request): Response
{
$key = $request->header('Idempotency-Key');
if (!$key) {
return $this->json(['error' => 'Idempotency-Key is required'], 400);
}
return $this->idempotency->run($key, function () use ($request) {
return $this->orders->create($request->validated());
});
}
Važan detalj nije oblik kontrolera. To su dizajn pohrane i transakcija iza njega: ključ mora biti povezan s opsegom zahtjeva, istodobni zahtjevi koji ga koriste moraju se sigurno obraditi, a dovršeni odgovor mora ostati dohvatljiv tijekom odgovarajućeg razdoblja čuvanja.
Neka baza podataka štiti istinu
Validacija aplikacije vrijedna je, ali nije zamjena za ograničenja baze podataka. Dva zahtjeva mogu u istom trenutku proći provjeru jedinstvenosti na razini aplikacije. Jedinstveni indeks sprječava da oba budu potvrđena.
Koristite primarne ključeve, strane ključeve tamo gdje odražavaju stvaran odnos, jedinstvena ograničenja za poslovne invarijante i transakcije za promjene koje moraju uspjeti zajedno. Neka transakcije budu usmjerene: držanje transakcije otvorenom tijekom poziva vanjskog API-ja povećava vrijeme zaključavanja i pretvara udaljeni prekid rada u sukob u bazi podataka.
Indeksi trebaju slijediti uočene obrasce pristupa. Indeks je koristan kada podržava filtriranje, spajanje ili sortiranje određenog upita; nije ukras za svaki stupac. Pregledajte spore upite sa stvarnim parametrima i realističnim volumenom podataka. Zatim pregledajte plan izvršavanja prije nego što proglasite upit ili ORM relaciju problemom.
Odvojite lokalnu potvrdu od vanjske isporuke
Kada ažuriranje baze podataka mora pokrenuti e-poruku, webhook ili nizvodni događaj, izbjegavajte tretirati mrežni poziv unutar procesa kao dio transakcije. Pouzdan obrazac je outbox: zapišite poslovnu promjenu i zapis događaja u istoj transakciji, a zatim neka radnik isporuči događaje na čekanju. Isporuka se može dogoditi više puta, pa potrošači i dalje trebaju idempotentnu obradu.
- Potvrdite mjerodavno stanje i zapis događaja zajedno.
- Obrađujte zapise na čekanju asinkrono uz ograničene ponovne pokušaje.
- Bilježite neuspjehe dovoljno jasno da ih operateri mogu istražiti.
- Učinite potrošače sigurnima kada se događaj ponovno isporuči.
To ne uklanja kvarove. Čini ih jasnima i oporavljivima.
Koristite asinkroni rad za zaštitu putanje zahtjeva
Web zahtjev obično bi trebao raditi samo ono što je potrebno za stvaranje točnog i pravovremenog odgovora. Obrada slika, izrada izvještaja, skupne obavijesti i nekritične integracije snažni su kandidati za redove. Dobit nije samo brzina; ona je izolacija. Spor pružatelj e-pošte ne bi trebao iscrpiti iste radnike za zahtjeve koji su potrebni za posluživanje stranica računa.
Redovi zahtijevaju disciplinu. Svaki zadatak treba vremensko ograničenje, politiku ponovnih pokušaja i vidljivo završno stanje neuspjeha. Ponovni pokušaji trebaju biti ograničeni i ne smiju ponavljati nesigurne nuspojave bez idempotentnosti. Zadatak koji uvijek ne uspijeva nije kandidat za ponavljanje zauvijek; on je operativni signal.
Neka Docker bude ponovljivo okruženje, a ne tajanstvena kutija
Spremnici mogu učiniti lokalni razvoj i implementaciju dosljednijima, ali samo kada konfiguracija ostane promišljena. Izgradite nepromjenjivu sliku aplikacije, ubrizgajte konfiguraciju specifičnu za okruženje tijekom izvođenja i držite podatke sa stanjem izvan spremnika aplikacije. Spremnik baze podataka može biti praktičan u razvoju; trajnost u produkciji ovisi o upravljanoj pohrani, trajnim volumenima, sigurnosnim kopijama i postupku obnove koji je stvarno testiran.
Za PHP, vrijeme izvođenja treba izlagati jasno ponašanje provjera zdravlja. Provjera živosti odgovara na pitanje treba li proces ponovno pokrenuti. Provjera spremnosti odgovara na pitanje može li sigurno primati promet. Nemojte uvjetovati spremnost svakom neobaveznom ovisnošću; inače prekid rada nepovezane integracije može nepotrebno ukloniti zdrav kapacitet.
Mjerite prije optimizacije
Rad na performansama najučinkovitiji je kada počinje simptomom vidljivim korisniku i dokazima. Pratite trajanje zahtjeva, stope pogrešaka, dubinu reda, latenciju baze podataka i zasićenje resursa. Dodajte strukturirane zapise s identifikatorom zahtjeva ili korelacije kako bi se neuspjeli API poziv mogao pratiti kroz radnike i ovisne servise.
Zatim optimizirajte ograničenu putanju. To može biti N+1 upit, nedostajuća paginacija, pretjerana serijalizacija, prevelik teret ili skup veza pod pritiskom. Predmemoriranje može pomoći, ali uvodi odluke o poništavanju i dosljednosti. Predmemorirajte podatke samo kada je zahtjev za svježinom razumljiv te definirajte kako se predmemorija popunjava, istječe i zaobilazi tijekom incidenta.
Gradite za sljedeću promjenu
Održivost je otpornost kroz vrijeme. Male promjene koje se mogu pregledati, planovi migracije koji omogućuju sigurno vraćanje, automatizirani testovi oko poslovnih pravila i jasno vlasništvo nad operativnim nadzornim pločama smanjuju trošak prilagodbe. Cilj nije predvidjeti svaki budući zahtjev. Cilj je izbjeći da uobičajena promjena djeluje opasno.
Najbolja arhitektura rijetko je najveća zbirka komponenti. To je ona koja važno ponašanje čini lakim za pronaći, kvarove lakima za ograničiti, a odluke lakima za izmijeniti. Iza skupa tehnologija, to je trajna prednost: sustav koji se može nastaviti razvijati bez gubitka oslonca.