Iznad CRUD-a: Arhitektura PHP servisa za dugotrajnost
CRUD je mjesto gdje većina pozadinskih servisa počinje, a ne gdje se dokazuju. Stvaranje, čitanje, ažuriranje i brisanje zapisa mogu demonstrirati okvir, ali produkcijski sustavi procjenjuju se prema težim pitanjima: Što se događa kada ovisnost zastane? Kada klijent ponovi zahtjev? Kada dva zahtjeva izmijene isti resurs? Kada implementacija promijeni pretpostavku koju nitko nije zapisao?
Otporan PHP servis nije onaj s najviše slojeva. To je onaj čije važno ponašanje ostaje razumljivo pod opterećenjem, pri djelomičnim kvarovima, promjenama i redovitom održavanju. Arhitektura bi trebala učiniti sretan put jasnim, ali nesretne putove namjernima.
Počnite s granicama, a ne mapama
Poznata struktura direktorija može stvoriti privid arhitekture bez stvarnog razdvajanja. Korisnije je pitanje: koji kod smije znati za koju pojedinost?
HTTP kontroler trebao bi prevesti dolazni zahtjev u slučaj uporabe aplikacije i njegov rezultat u odgovor. Ne bi trebao odlučivati o upitima prema bazi podataka, izračunavati poslovna pravila ili potajno pozivati API treće strane. Isto tako, odluke domene ne bi trebale ovisiti o objektima zahtjeva, ORM modelima ili pomoćnim funkcijama okvira.
Praktičan servis često ima tri široka područja:
- Transport: kontroleri, validacija zahtjeva, međuprogrami za autentifikaciju i oblikovanje odgovora.
- Aplikacija: slučajevi uporabe koji koordiniraju rad, određuju granice transakcija i provode pravila tijeka rada.
- Infrastruktura: repozitoriji baze podataka, redovi, predmemorije, pružatelji e-pošte i vanjski HTTP klijenti.
Ovo nije argument za ceremonijalne apstrakcije oko svake klase. Sučelje repozitorija isplati se kada štiti značajnu aplikacijsku logiku od pojedinosti pohrane ili omogućuje korisnu alternativnu implementaciju. Omatanje jednog ORM poziva samo zato što su „repozitoriji čišći” često dodaje posredovanje bez smanjenja rizika.
Učinite promjene stanja eksplicitnima i transakcijskima
Mnoge greške nastaju kada servis poslovnu operaciju tretira kao niz nepovezanih zapisa. Razmotrite naručivanje: rezervirati zalihu, stvoriti narudžbu i zabilježiti revizijski događaj. Ako se zaliha smanji, ali stvaranje narudžbe ne uspije, sustav je stvorio stanje koje ne može lako objasniti.
Promjene koje moraju zajedno uspjeti ili ne uspjeti smjestite unutar transakcije baze podataka. Ta transakcija neka bude mala: zaključajte samo ono što je nužno, izbjegavajte mrežne pozive dok je otvorena i vratite rezultat koji predstavlja dovršeno stanje.
final class PlaceOrder
{
public function __construct(
private Connection $db,
private Inventory $inventory,
private Orders $orders,
private Outbox $outbox,
) {}
public function handle(PlaceOrderCommand $command): Order
{
return $this->db->transaction(function () use ($command): Order {
$this->inventory->reserve($command->productId, $command->quantity);
$order = $this->orders->create(
customerId: $command->customerId,
productId: $command->productId,
quantity: $command->quantity,
);
$this->outbox->record('order.placed', ['order_id' => $order->id]);
return $order;
});
}
}
Stavka odlaznog spremnika je važna. Izravno slanje poruke posredniku nakon potvrde transakcije može ne uspjeti, ostavljajući narudžbu pohranjenom, ali sustave nizvodno nesvjesnima. Slanje prije potvrde može objaviti događaj o transakciji koja se kasnije poništi. Bilježenje događaja uz promjenu u bazi podataka daje radniku pouzdanu stavku za kasniju objavu. Radnik i dalje mora podnositi dvostruku objavu, ali sustav ima put oporavka.
Dizajnirajte API-je za ponavljanja i neslaganja
Mreže su nepouzdane na uobičajene načine. Klijentu može isteći vrijeme nakon što poslužitelj uspješno stvori resurs. Ako klijent ponovi neidemponentan zahtjev, servis može stvoriti dvije narudžbe, plaćanja ili obavijesti.
Za operacije kod kojih je dupliciranje štetno, prihvatite ključ idempotentnosti i pohranite ga s dovršenim rezultatom. Ponavljanje istog ključa trebalo bi vratiti izvorni ishod umjesto ponovnog izvršavanja operacije. Odredite što se događa ako se isti ključ ponovno upotrijebi s različitim unosom: obično ga odbijte, jer prešutno prihvaćanje sukobljene namjere znatno otežava rješavanje incidenata podrške.
Istovremena obrada zaslužuje jednaku pozornost. Slijed „provjeri pa ažuriraj” može preprodati zalihu ako dva zahtjeva oba vide stanje zalihe prije nego što ijedan upiše promjenu. Upotrijebite bazu podataka kao autoritet za istovremenu obradu: prikladno zaključavanje retka, atomsko uvjetno ažuriranje ili optimistička provjera verzije mogu učiniti pravilo provedivim. Ispravan izbor ovisi o konkurentnosti i cijeni ponavljanja, ali oslanjanje na vremensko usklađivanje aplikacije uopće nije izbor.
Greške su dio ugovora
Servis bi trebao razlikovati nevaljan unos, nedostajuće resurse, zabranjene radnje, sukobe i privremene kvarove ovisnosti. Klijenti se ne mogu dobro ponašati ako svaki problem postane generička pogreška poslužitelja.
Neka odgovori na pogreške budu stabilni i sigurni. Vratite strojno čitljiv kod, ljudima čitljivu poruku prikladnu pozivatelju i identifikator zahtjeva kada je dostupan. Interni kontekst bilježite zasebno. Pojedinosti baze podataka, zapisi stoga, vjerodajnice i tijela odgovora trećih strana u pravilu ne pripadaju ni blizu javnog API odgovora.
Koristite asinkroni rad bez skrivanja odgovornosti
Redovi su izvrsni za rad koji ne mora biti dovršen prije HTTP odgovora: e-poštu, obradu slika, isporuku web-dojavnih poziva, indeksiranje pretrage i razgranate obavijesti. Nisu zamjena za odlučivanje o tome što se korisniku obećava.
Ako krajnja točka vrati uspjeh prije nego što se rad u redu dovrši, njezin ugovor trebao bi reći da je zahtjev prihvaćen, a ne da se dogodio svaki učinak nizvodno. Poslovi trebaju jasno ponašanje pri ponavljanju: klasificirajte prolazne kvarove, koristite ograničena ponavljanja s odgodom i pošaljite iscrpljene poslove na mjesto koje operateri mogu pregledati. Red koji beskonačno ponavlja trajnu pogrešku validacije nije otporan; samo je bučan.
Potrošači bi također trebali biti idempotentni. Isporuka se može dogoditi više puta, osobito pri rušenju radnika i vremenskom usklađivanju potvrda. Pohranite identifikator obrađene poruke ili učinite rezultirajući zapis prirodno sigurnim za ponavljanje.
Neka PHP bude operativno dosadan
PHP servisima koriste dosadne konvencije implementacije. Izgradite jedan nepromjenjivi artefakt aplikacije, konfiguraciju pružite kroz okruženje ili upravljani sustav konfiguracije, migracije baze podataka pokrećite kao kontrolirani korak implementacije i učinite proces vidljivim. Spremnici pomažu kada lokalna, testna i produkcijska okruženja čine dosljednijima; ne uklanjaju potrebu za upravljanjem vezama prema bazi podataka, dozvolama za datoteke, vremenskim ograničenjima ili gracioznim gašenjem.
Za HTTP klijente postavite eksplicitna vremenska ograničenja povezivanja i ukupna vremenska ograničenja. Ovisnost koja se zaglavi ne bi trebala neograničeno trošiti PHP radnike. Ponovno koristite veze kada to izvođenje i klijent podržavaju, ali postavite ograničenja istovremenosti prema nizvodnim sustavima kako pružatelj koji se bori ne bi uvukao cijeli servis u kaskadu.
Rad na performansama trebao bi slijediti mjerenje. N+1 upiti, nedostajući indeksi, preveliki tereti i neograničena paginacija česti su jer ih je lako uvesti, a teško primijetiti u malim skupovima podataka. Dodajte vidljivost upita, definirajte ograničenja paginacije i proučite spori put prije nego što posegnete za predmemorijom. Predmemoriranje pomaže samo kada se razumiju njegova pravila poništavanja i zastarijevanja.
Gradite za sljedeću promjenu
Održivost je uglavnom sposobnost promjene jednog pravila bez nagađanja koje će se nepovezano ponašanje pokvariti. Imenujte slučajeve uporabe prema poslovnim radnjama, konfiguraciju držite centraliziranom, testirajte ponašanje na granicama i učinite vidljivost značajkom, a ne hitnim dodatkom. Strukturirani zapisi, provjere zdravlja, metrike i tragovi trebali bi odgovoriti na pitanje što se dogodilo bez potrebe za produkcijskom sesijom ljuske.
Cilj nije savršen dijagram arhitekture. Cilj je servis koji može podnijeti uobičajeni kvar, iskreno komunicirati svoje stanje i razvijati se bez pretvaranja svakog malog zahtjeva u rizičnu ekspediciju. CRUD može biti površina sustava. Ustrajnost je ono zbog čega ga se isplati pokretati.