Razvoj

Beyond Abstraction: Building PHP Services That Actually Work

Iznad apstrakcije: izrada PHP servisa koji zaista rade

Većina PHP servisa ne otkazuje zato što PHP nije sposoban. Otkazuju zato što baza koda postane odanija apstrakciji nego poslu koji servis zapravo mora obaviti: prihvaćanju ulaza, zaštiti podataka, pozivanju ovisnosti, preživljavanju djelomičnih neuspjeha i pružanju operaterima dovoljno dokaza za djelovanje.

Dobro pozadinsko inženjerstvo nije natjecanje u stvaranju najviše slojeva. To je disciplina koja važno ponašanje čini lakim za pronaći, lakim za testirati i teškim za pogrešno koristiti. PHP u potpunosti može podržati tu disciplinu kada su njegove granice pažljivo odabrane.

Započnite s korisnom granicom

Granica servisa trebala bi odgovarati smislenoj odgovornosti, a ne pomodnom obrascu. Krajnja točka koja, primjerice, stvara narudžbu treba validaciju, odluku o autorizaciji, transakciju baze podataka i možda poruku za daljnji rad. To su stvarne brige. Uvođenje generičkog „managera”, „handlera”, „factoryja” i „providera” za svaku od njih možda ih neće razjasniti.

Praktičan oblik često je jednostavan: HTTP brige zadržite u kontroleru, slučaj uporabe smjestite u aplikacijski servis, a perzistenciju ili vanjske sustave izolirajte iza malih sučelja. Osnovni slučaj uporabe trebao bi se čitati poput poslovne operacije koju predstavlja.

final class CreateOrder
{
    public function __construct(
        private OrderRepository $orders,
        private TransactionManager $transactions,
        private OrderEvents $events,
    ) {}

    public function handle(CreateOrderCommand $command): Order
    {
        return $this->transactions->run(function () use ($command): Order {
            $order = Order::fromCommand($command);

            $this->orders->save($order);
            $this->events->record(new OrderCreated($order->id()));

            return $order;
        });
    }
}

Ovo nije „čisto” zato što ima sučelja. Korisno je zato što su namjera transakcije, perzistencije i događaja vidljive. Čitatelj može odmah postaviti pravo operativno pitanje: kako se zabilježeni događaji isporučuju nakon potvrde transakcije?

Neka neuspjeh bude prvoklasni ulaz u dizajn

Kod za sretan put obično je kratak. Produkcijsko ponašanje živi u vremenskim ograničenjima, ponovnim pokušajima, dupliciranim zahtjevima, nedostupnim bazama podataka, isteklim vjerodajnicama i porukama isporučenima više puta. To nisu iznimni detalji koje treba naknadno dodati; oni oblikuju dizajn od samog početka.

Razmotrite zahtjev koji naplaćuje korisnika i stvara narudžbu. Transakcija baze podataka ne može atomarno uključiti udaljenog pružatelja platnih usluga. Ako naplata uspije, a zapis u bazu podataka ne uspije, slijepo ponovno pokušavanje moglo bi ponovno naplatiti korisnika. Odgovor nije veća transakcija. To je izričita strategija idempotentnosti: prihvatite ključ koji je pružio klijent, pohranite rezultat povezan s tim ključem i osigurajte da isti ključ vraća izvorni ishod.

Slično tome, izravno objavljivanje događaja nakon potvrde baze podataka ima prazninu. Proces se može zaustaviti nakon potvrđivanja narudžbe, ali prije objavljivanja događaja. Tablica outbox zatvara tu prazninu: zapišite narudžbu i outbox zapis u jednoj transakciji baze podataka, a zatim zasebnom radniku prepustite isporuku zapisa na čekanju. Isporuka i dalje mora biti idempotentna jer radnik može uspješno poslati poruku i ne uspjeti prije nego što je označi dovršenom.

  • Postavite vremenska ograničenja za izlazne HTTP i veze s bazom podataka.
  • Pokušavajte ponovno samo neuspjehe koji su vjerojatno prolazni, uz ograničen broj pokušaja.
  • Koristite ključeve idempotentnosti za naredbe vidljive izvana.
  • Zabilježite dovoljno konteksta za istraživanje neuspjeha bez zapisivanja tajni.
  • Dizajnirajte potrošače tako da toleriraju duplicirane poruke i, gdje je primjenjivo, isporuku promijenjenog redoslijeda.

Neka model baze podataka bude iskren

ORM-ovi mogu ubrzati uobičajenu perzistenciju, ali ne uklanjaju ponašanje baze podataka. Upit unutar petlje i dalje je upit unutar petlje. Indeks koji nedostaje i dalje je indeks koji nedostaje. Transakcija koja ostaje otvorena tijekom pozivanja drugog servisa i dalje je izvor zastoja i neuspjeha.

Modelirajte ograničenja tamo gdje pripadaju. Ako adresa e-pošte mora biti jedinstvena, provedite to jedinstvenim ograničenjem baze podataka, a zatim nastali sukob prevedite u koristan odgovor aplikacije. Provjere na strani aplikacije poboljšavaju korisničko iskustvo, ali istu provjeru mogu proći istodobni zahtjevi. Samo baza podataka može provesti invarijantu u trenutku zapisa.

Za važne upite za čitanje pregledajte upit koji se stvarno izvršava i njegov plan izvršavanja. Dajte prednost paginaciji koja odgovara obrascu pristupa. Paginacija s pomakom može biti u redu za male administrativne popise; za velik, često promjenjiv feed, keyset paginacija temeljena na stabilnom, indeksiranom poretku često je predvidljivija.

Transakcije trebaju biti kratke i namjerne

Koristite transakcije za očuvanje povezanih lokalnih promjena, a ne kao opći omotač oko cijelog zahtjeva. Validirajte ulaze i obavite spore udaljene pozive prije otvaranja transakcije kada je to moguće. Unutar transakcije izvršite nužne zapise, provedite invarijante i potvrdite transakciju. To skraćuje trajanje zaključavanja i olakšava rezoniranje o zastoju.

Spremnici bi trebali smanjiti iznenađenja

Docker pomaže kada definicija spremnika izričito navodi pretpostavke izvršavanja. Produkcijska slika trebala bi sadržavati aplikaciju i samo izvršne ovisnosti koje su joj potrebne. Alati za izgradnju, alati za testiranje i razvojna konfiguracija obično pripadaju zasebnoj fazi izgradnje ili lokalnom razvojnom okruženju.

FROM php:8.3-cli AS runtime

WORKDIR /app
COPY . /app

CMD ["php", "bin/worker.php"]

Ovaj je primjer namjerno malen, a ne univerzalna produkcijska slika. Stvarnom servisu također su potrebni promišljen korak instalacije ovisnosti, ne-root korisnik tijekom izvođenja gdje je to prikladno, konfiguracija isporučena kroz njegovo okruženje implementacije i strategija provjere zdravlja koja odražava ono što proces zaista može poslužiti. Nemojte učiniti provjeru zdravlja ovisnom o sporom API-ju treće strane osim ako je ta ovisnost potrebna za svaki zahtjev.

Konfiguraciju treba validirati pri pokretanju. Rani neuspjeh zato što nedostaje potrebni URL baze podataka sigurniji je od prihvaćanja prometa i neuspjeha svakog zahtjeva. Tajne držite izvan kontrole izvornog koda, zapisa, poruka o iznimkama i slojeva slike spremnika.

Promatranje je dio sučelja

Servis koji ne može objasniti što radi skup je za upravljanje. Zapisi bi trebali biti dovoljno strukturirani za filtriranje i korelaciju. Uključite identifikator zahtjeva ili praćenja, naziv operacije, ishod i sigurne identifikatore poput ID-a narudžbe. Izbjegavajte ispisivanje cijelih tijela zahtjeva, zaglavlja za autorizaciju, lozinki, tokena ili pojedinosti o plaćanju.

Metrike trebaju odgovarati na operativna pitanja: koliko zahtjeva ne uspijeva, koliko traju ključne operacije, rastu li redovi čekanja i istječu li pozivi ovisnostima. Upozorenja bi trebala biti vezana uz simptome koji utječu na korisnike ili jasne rizike kapaciteta, a ne uz svaku iznimku koju je ponovni pokušaj uspješno riješio.

Odaberite dosadnu jasnoću umjesto ceremonijalne arhitekture

Apstrakcije zaslužuju svoje mjesto kada skrivaju promjenjive detalje, stvaraju stabilnu granicu za testiranje ili olakšavaju provođenje pravila koje se ponavlja. Ne zaslužuju ga samo zato što stablo direktorija izgleda spremno za poduzeće.

Najjači PHP servisi rijetko su zagonetni. Njihovi zahtjevi imaju jasne putanje, njihovi podaci imaju provediva pravila, njihove ovisnosti imaju ponašanje pri neuspjehu, a njihova implementacija ima izričite pretpostavke. Gradite za te stvarnosti. Kada je sustav pod pritiskom, jasnoća nije estetsko dotjerivanje; ona je značajka koja održava servis funkcionalnim.

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.