Razvoj

Pragmatic PHP: Building Systems That Endure the Test of Time

Pragmatični PHP: Izgradnja sustava koji odolijevaju testu vremena

Najvrjedniji pozadinski sustavi rijetko su oni s najmodernijim tehnološkim paketom. To su oni koji ostaju razumljivi kada se zahtjevi promijene, promet neravnomjerno raste, ovisnost zakaže i novi programer treba sigurno unijeti promjenu u petak poslijepodne.

PHP je prikladan za ovakvu vrstu rada kada se prema njemu odnosi kao prema praktičnom inženjerskom alatu, a ne zbirci prečaca. Njegova snaga nije u tome što uklanja teške odluke. Njegova je snaga u tome što timovima omogućuje jasno izraziti te odluke, isporučiti koristan softver i nastaviti ga poboljšavati bez pretvaranja svake aplikacije u arhitektonski eksperiment.

Počnite s jednostavnim granicama

PHP aplikaciji koju je moguće održavati potrebne su jasne granice prije nego složeni obrasci. Obrada HTTP zahtjeva, pravila aplikacije, perzistencija i vanjske integracije trebaju imati različite odgovornosti. To ne zahtijeva veliki okvir ni strukturu direktorija koja nalikuje udžbeniku. Zahtijeva odgovor na jednostavno pitanje: gdje bi ova odluka trebala pripadati?

Kontroler bi trebao prevesti zahtjev u radnju aplikacije i rezultat natrag u odgovor. Ne bi trebao sadržavati pravila određivanja cijena, sastavljati SQL nizove ni odlučivati kako usluga treće strane ponavlja pokušaje. Te je pojedinosti teško testirati, a još teže ponovno upotrijebiti.

final class CreateOrderController
{
    public function __invoke(CreateOrderRequest $request): JsonResponse
    {
        $order = $this->orders->create(
            new CreateOrderCommand(
                customerId: $request->customerId(),
                items: $request->items()
            )
        );

        return new JsonResponse([
            'id' => $order->id(),
            'status' => $order->status(),
        ], 201);
    }
}

Aplikacijska usluga može koordinirati rad: provjeriti poslovne uvjete, po potrebi otvoriti transakciju, pohraniti narudžbu i zatražiti naknadnu radnju. Repozitoriji bi se trebali baviti pitanjima pohrane. Klijent za integraciju trebao bi znati kako pozvati vanjski svijet. Svaki sloj ostaje dovoljno malen da se o njemu može razmišljati kada nešto pođe po zlu.

Dizajnirajte API-je za promjene, a ne samo za današnje potrebe

API je obećanje. Kada mobilni klijenti, partneri ili druga interna usluga počnu ovisiti o obliku njegova odgovora, njegova promjena postaje problem koordinacije. Praktičan odgovor nije preventivno verzionirati svaku krajnju točku. Riječ je o tome da ugovori od početka budu promišljeni.

  • Koristite stabilna imena resursa i predvidive HTTP statusne kodove.
  • Validirajte ulaz na granici i vraćajte strukturirane, korisne pogreške koje omogućuju djelovanje.
  • Držite interna polja baze podataka odvojenima od javnih polja odgovora.
  • Operacije pisanja učinite sigurnima za ponavljanje kada klijenti mogu ponoviti zahtjeve.
  • Izričito dokumentirajte ponašanje paginacije, filtriranja, sortiranja i autorizacije.

Idempotentnost zaslužuje posebnu pozornost. POST zahtjev povezan s plaćanjem može se ponoviti nakon isteka vremena, čak i ako je poslužitelj već dovršio rad. Ključ idempotentnosti pohranjen uz operaciju omogućuje poslužitelju da vrati izvorni ishod umjesto stvaranja duplicirane naplate ili narudžbe. To nije rubni slučaj; to je uobičajeno ponašanje distribuiranih sustava.

Slično tome, izbjegavajte da ID-jevi baze podataka budu cjelokupan dizajn vašeg API-ja. Javni identifikator može biti neproziran, dok interni numerički ključevi ostaju učinkoviti za spajanja. To modelu pohrane daje prostor za razvoj i smanjuje slučajno otkrivanje pojedinosti implementacije.

Neka baza podataka odradi svoj dio

Mnogi problemi s performansama i ispravnošću počinju kada aplikacija bazu podataka tretira kao pasivnu pohranu. Relacijska baza podataka može osigurati jedinstvenost, odnose stranih ključeva, transakcije i korisne indekse. Dopustite joj to.

Na primjer, ako adresa e-pošte mora biti jedinstvena, validacija na razini aplikacije korisna je za prijateljsku poruku o pogrešci, ali nije dovoljna. Dva istodobna zahtjeva mogu oba proći tu validaciju prije nego što ijedan umetne redak. Ograničenje jedinstvenosti konačni je autoritet; aplikacija bi trebala uhvatiti njegovo kršenje i preslikati ga u koristan odgovor.

CREATE TABLE users (
    id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    email VARCHAR(255) NOT NULL,
    created_at TIMESTAMP NOT NULL,
    CONSTRAINT users_email_unique UNIQUE (email)
);

Indeksi bi trebali slijediti stvarne obrasce upita. Ako uobičajeni upit odabire nedavne narudžbe za jednog kupca, indeks koji počinje s customer_id, a zatim s created_at, može biti prikladan. Nasumično dodavanje indeksa može usporiti pisanja i trošiti memoriju, zato pregledajte planove upita i mjerite upite koji su važni.

Transakcije bi trebale štititi koherentnu poslovnu promjenu, a ne samo obuhvatiti svaki poziv bazi podataka. Neka budu kratke. Ne držite transakciju otvorenom dok čekate HTTP zahtjev, šaljete e-poštu ili obrađujete veliku datoteku. Najprije trajno pohranite stanje, zatim asinkrono obradite spore nuspojave ili one sklone neuspjehu kada to arhitektura podržava.

Koristite Docker kako biste smanjili iznenađenja

Spremnici su najkorisniji kada lokalni razvoj, testiranje i implementaciju čine dosljednima. PHP aplikacija obično ima koristi od male, eksplicitne slike: fiksirajte verziju PHP-a, instalirajte samo potrebna proširenja, kopirajte manifeste ovisnosti prije koda aplikacije kako biste poboljšali predmemoriranje izgradnje i pokrećite Composer na ponovljiv način.

Konfiguracija bi trebala dolaziti kroz postavke specifične za okruženje, a ne kroz izmjene izvornih datoteka. Tajne trebaju siguran mehanizam u vrijeme implementacije, umjesto da budu ugrađene u slike ili predane u repozitorij. Spremnik bi također trebao imati jedan jasan zadatak: posluživati aplikaciju, pokretati radnika ili izvršavati zakazanu naredbu. Spajanje svakog procesa u jedan spremnik čini kvarove i skaliranje manje jasnima.

Docker ne uklanja operativnu odgovornost. Provjere stanja moraju odražavati može li aplikacija doista posluživati koristan promet, zapisnici bi trebali ići na standardni izlaz i standardnu pogrešku u strukturiranom obliku kada je moguće, a trajni podaci baze podataka moraju živjeti izvan efemernog spremnika aplikacije.

Optimizirajte put za koji možete dokazati da je spor

Rad na performansama najučinkovitiji je kada počinje dokazima. Sporu krajnju točku može uzrokovati upit bez indeksa, ponovljeni upiti unutar petlje, preveliki serijalizirani odgovori, poziv udaljenog API-ja ili nedovoljan kapacitet PHP radnika. Ti uzroci zahtijevaju različita rješenja.

Klasični problem ORM-a je N+1 upit: učitajte popis zapisa, zatim lijeno učitajte povezani zapis za svaku stavku. Kôd se čita prirodno, ali stranica s pedeset narudžbi može postati pedeset i jedan upit prema bazi podataka. Učitavanje unaprijed, ciljani spoj ili namjenski upit za čitanje mogu dramatično smanjiti taj rad bez uvođenja predmemorije.

Predmemoriranje je vrijedno kada su podaci skupi za izračunavanje i dovoljno stabilni, ali stvara odgovornosti za invalidaciju i dosljednost. Predmemorirajte jasno definiran rezultat s poznatim trajanjem. Uključite relevantne dimenzije u ključ predmemorije, poput lokalizacije ili opsega računa. Najvažnije, održite sustav ispravnim kada je predmemorija prazna, zastarjela ili nedostupna.

Učinite kvar temeljnim ulazom u dizajn

Mreže zakažu, redovi čekanja kasne, baze podataka odbijaju veze, a implementacije povremeno otkriju previdjenu pretpostavku. Robusni PHP sustavi razlikuju pogreške koje treba ponovno pokušati, pogreške koje treba prijaviti pozivatelju i pogreške koje zahtijevaju ljudsku pozornost.

Ponovni pokušaji zahtijevaju ograničenja i oprez. Ponovni pokušaj privremenog kvara veze može biti razuman; ponavljanje pogreške validacije rasipno je. Za vanjske zahtjeve koristite vremenska ograničenja, ograničene ponovne pokušaje i odgodu. Osigurajte da ponovni pokušaj ne može ponoviti nepovratnu radnju, osim ako usluga primatelj pruža mehanizam idempotentnosti.

Promatranje pretvara nejasne incidente u rješive probleme. Bilježite smisleni kontekst bez otkrivanja tajni ili osobnih podataka. Priložite identifikator zahtjeva ili korelacije kroz HTTP pozive i poslove u redu čekanja. Pratite iznimke, vremena odgovora, kvarove reda čekanja i opterećenje baze podataka. Cilj nije više nadzornih ploča; cilj je dovoljno dokaza da se objasni što se dogodilo.

Odaberite najjednostavniji dizajn koji čuva mogućnosti

Pragmatično inženjerstvo nije protiv arhitekture. To je arhitektura s jasnim modelom troškova. Modularni monolit s dobrim granicama često je lakše implementirati, otklanjati pogreške i razvijati nego zbirku usluga koje se moraju koordinirati preko mreže. Izdvojite uslugu kada potrebe za neovisnom implementacijom, skaliranjem, vlasništvom ili pouzdanošću opravdavaju njezin operativni trošak.

Ista procjena vrijedi za apstrakcije. Uvedite sučelje kada postoji stvarna granica, poput obrade plaćanja ili pohrane datoteka, a ne zato što bi svaka klasa jednog dana mogla imati dvije implementacije. Dajte prednost kodu koji današnja pravila čini očitima, a istodobno ostavlja mogućima sutrašnje promjene.

Sustavi traju kada ih njihovi timovi mogu razumjeti pod pritiskom. Jasni ugovori, pouzdana ograničenja podataka, promišljeno postupanje pri kvarovima, izmjeren rad na performansama i umjerena arhitektura stvaraju tu prednost. PHP može sve to izvrsno podržati kada je vodeće načelo jednostavno: izgradite ono što rješava stvarni problem, a zatim ga ostavite lakšim za promjenu nego što ste ga zatekli.

Portret autora bloga

Mihajlo

Ja sam Mihajlo — programer vođen znatiželjom, disciplinom i stalnom željom da stvorim nešto smisleno. Dijelim uvide, tutorijale i besplatne usluge kako bih pomogao drugima da pojednostave svoj rad i rastu u svijetu softvera i umjetne inteligencije koji se neprestano razvija.