Iznad okvira: projektiranje PHP sustava za dugoročnu održivost
Okvir može učiniti da PHP aplikacija djeluje dovršeno mnogo prije nego što je stvarno dobro dizajnirana. Rute se razrješavaju, kontroleri vraćaju JSON, migracije se pokreću, a Docker pokreće cijeli skup jednom naredbom. To su korisne prednosti. Same po sebi, međutim, ne čine arhitekturu.
Dugoročna održivost proizlazi iz odluka koje ostaju razumljive kada baza koda raste, zahtjevi se mijenjaju i izvorna implementacija više nije svježa ni u čijem sjećanju. Okviri bi trebali ubrzavati takve odluke, a ne prikrivati ih.
Započnite s granicama, a ne direktorijima
Mnoge PHP aplikacije počinju poznatom strukturom: kontroleri, modeli, servisi, repozitoriji, poslovi i naredbe. To je razumna polazna točka, ali mape ne stvaraju granice. Kontroler i dalje može sadržavati pravila određivanja cijena, ORM model i dalje može slati e-poruke, a „servis” i dalje može postati neodređeno mjesto za prikupljanje nepovezane logike.
Trajniji pristup jest organizirati važan kôd oko poslovnih sposobnosti i učiniti ovisnosti eksplicitnima. Za sustav naručivanja to mogu biti koncepti kao što su narudžbe, katalog, plaćanja i ispunjenje narudžbi. Svaka sposobnost posjeduje vlastita pravila i ostatku aplikacije izlaže malo, namjerno definirano sučelje.
Praktično je pitanje jednostavno: ako novo pravilo promijeni način na koji se narudžba kreira, gdje bi razvojni programer trebao tražiti? Ako je odgovor „u nekoliko kontrolera, observeru, hooku modela i osluškivaču reda”, granica već propušta.
HTTP odgovornosti zadržite na rubu
Kontroleri bi trebali prevoditi HTTP u ulaz aplikacije, a izlaz aplikacije u HTTP. Trebali bi validirati oblik zahtjeva, odabrati odgovarajući statusni kôd i delegirati stvarni slučaj uporabe. To omogućuje pozivanje osnovnog tijeka rada iz naredbe, radnika reda ili testa bez ponovnog stvaranja HTTP zahtjeva.
final class PlaceOrderController
{
public function __invoke(PlaceOrderRequest $request, PlaceOrder $useCase): JsonResponse
{
$order = $useCase->handle(
customerId: $request->user()->id,
items: $request->validated('items')
);
return response()->json([
'id' => $order->id(),
'status' => $order->status(),
], 201);
}
}
Primjer ne zahtijeva opsežan program apstrakcije. Njegova je vrijednost u tome što tijek rada narudžbe ima jasno mjesto, dok kontroler ostaje odgovoran za web-granicu.
Bazu podataka koristite promišljeno
PHP sustavi često nasljeđuju svoju arhitekturu od ORM-a. To može biti produktivno sve dok ponašanje baze podataka ne postane iznenađujuće: implicitni upiti u petljama, slučajna pisanja tijekom čitanja, događaji modela sa skrivenim nuspojavama ili transakcije koje obuhvaćaju samo dio poslovne operacije.
Prema bazi podataka odnosite se kao prema važnoj ovisnosti sa stvarnim ograničenjima. Definirajte jedinstvenost u bazi podataka kada poslovna pravila zahtijevaju jedinstvenost. Koristite strane ključeve ondje gdje predstavljaju stabilan odnos. Dodajte indekse na temelju stvarnih obrazaca upita, a ne neodređenog uvjerenja da „više indeksa” znači brže upite.
Na primjer, ako API često dohvaća nedavne narudžbe kupca, relevantni upit i indeks treba razmatrati zajedno. Upit oblikovan oko customer_id, sortiran prema vremenu kreiranja, zahtijeva strategiju indeksiranja prikladnu tom obrascu pristupa. Točan indeks ovisi o mehanizmu baze podataka i planu upita, stoga ga provjerite alatima koje vaša baza podataka pruža, umjesto da pretpostavite kako je upit koji generira ORM učinkovit.
Uskladite transakcije s poslovnom radnjom
Transakcija nije samo zaštitni omotač oko svakog pisanja. Ona definira atomsku jedinicu: ili narudžba i njezine stavke postoje zajedno, ili ne postoji nijedno. Posao unutar nje neka bude fokusiran i kratak. Mrežni pozivi, spore operacije nad datotekama i vanjske obavijesti obično ne pripadaju unutar transakcije baze podataka.
Kada radnja mora ažurirati lokalne podatke i naknadno obavijestiti drugi sustav, najprije zabilježite lokalno stanje i upotrijebite pouzdan mehanizam predaje za vanjski posao. Ključno je načelo dizajna izbjeći tvrdnju da je vanjski učinak uspio prije nego što je doista uspio.
Dizajnirajte API-je kao ugovore
API je obećanje klijentima, uključujući klijente koje održava vaš vlastiti tim. Čist kontroler ne pomaže mnogo ako API ima nedosljedna tijela pogrešaka, nedokumentirano ponašanje paginacije ili polja koja bez najave mijenjaju značenje.
Uspostavite dosljedan ugovor za odgovore o uspjehu i neuspjehu. Odlučite kako se prikazuju pogreške validacije, kako se formatiraju identifikatori, što znače metapodaci paginacije i uključuju li vremenske oznake podatke o vremenskoj zoni. Ti detalji sprječavaju nagađanja u sustavima koji ovise o API-ju.
- Koristite stabilne, smislenе nazive resursa.
- Validirajte unos na granici, a zatim provedite kritična pravila i u aplikacijskom sloju.
- Vraćajte pogreške koje klijenti mogu programski obraditi bez otkrivanja internih pojedinosti implementacije.
- Učinite idempotentnost eksplicitnim pitanjem za operacije koje klijenti mogu ponoviti.
- Verzionirajte samo kada se promjena ugovora ne može uvesti kompatibilno.
Ponovni pokušaji zahtijevaju posebnu pažnju. Klijent može ponovno poslati zahtjev jer nije primio odgovor, iako je poslužitelj dovršio izvornu operaciju. Za krajnju točku povezanu s plaćanjem ili kreiranjem narudžbe, ključ idempotentnosti pohranjen uz rezultirajuću operaciju može spriječiti da ponovni pokušaj stvori duplicirani rezultat. Implementacija također mora definirati što se događa kada isti ključ stigne s drukčijim unosom: treba ga odbiti, a ne tiho tretirati kao isti zahtjev.
Docker bi trebao smanjiti trenje, a ne sakriti produkciju
Docker je vrijedan kada lokalni razvoj čini predvidljivim: iste PHP ekstenzije, verzija baze podataka, proces radnika i konfiguracija usluge mogu se dosljedno pokrenuti kod svakog suradnika. Postaje manje koristan kada se razvojni spremnik smatra dokazom da je produkcijska implementacija ispravna.
Zadržite eksplicitnu konfiguraciju. Varijable okruženja korisne su za vrijednosti specifične za implementaciju, ali ih treba rano validirati. Nedostajući URL baze podataka ili neispravna postavka reda trebali bi jasno uzrokovati neuspjeh tijekom pokretanja ili provjere stanja, a ne se pojaviti kao zbunjujuća pogreška pod opterećenjem.
Odvojite probleme vremena izgradnje od problema vremena izvođenja. Ovisnosti bi trebale biti instalirane reproducibilno, aplikacijski kôd trebao bi biti namjerno zapakiran, a spremnici za vrijeme izvođenja trebali bi sadržavati samo ono što im je potrebno za posluživanje aplikacije. Isto načelo vrijedi i izvan spremnika: implementacija bi trebala biti prepoznatljiv artefakt s konfiguracijom koju pruža njezino okruženje.
Performanse počinju vidljivošću
Rad na performansama najučinkovitiji je kada počinje konkretnim opažanjem: sporom krajnjom točkom, zasićenim radnikom, upitom baze podataka sa skupim planom ili rastom memorije u dugotrajnom procesu. Prerano predmemoriranje može zamijeniti vidljiv problem latencije težim problemom dosljednosti.
Mjerite trajanje zahtjeva, broj upita baze podataka, neuspjehe reda i stope pogrešaka na načine koji razvojnom programeru omogućuju praćenje jedne operacije kroz sustav. Zabilježite dovoljno konteksta za dijagnosticiranje neuspjeha, ali izbjegavajte unositi vjerodajnice, tokene ili nepotrebne osobne podatke u zapisnike.
Zatim poboljšajte najuže usko grlo. Uklonite N+1 upit prije dodavanja predmemorije. Paginirajte neograničene skupove rezultata prije povećanja memorijskih ograničenja. Premjestite doista spor, neinteraktivan zadatak u red prije nego što HTTP zahtjev prisilite da ga čeka. Svaka promjena trebala bi imati jasan očekivani učinak i način provjere.
Održivost je svakodnevna praksa dizajna
Najjači PHP sustavi rijetko su oni s najviše obrazaca. To su oni u kojima razvojni programer može pratiti zahtjev, razumjeti pravila, sigurno promijeniti jedno ponašanje i provjeriti rezultat. To zahtijeva čitljivo imenovanje, fokusirane testove oko važnog ponašanja, promišljene granice i operativne povratne informacije nakon implementacije.
Okvir je i dalje moćan alat. Neka upravlja ponavljajućom mehanikom: usmjeravanjem, obradom zahtjeva, konfiguracijom, migracijama i točkama integracije. Ali trajne odluke zadržite vidljivima. Kada kôd izražava što sustav radi i zašto njegova pravila postoje, aplikacija može nadživjeti modu okvira koji ju je pomogao izgraditi.