Razvoj

Decouple Your PHP Services: Forge Robust APIs, Not Brittle Dependencies

Razdvojite svoje PHP servise: Izgradite robusne API-je, a ne krhke zavisnosti

PHP servis rijetko postaje težak zato što jedna klasa ima previše metoda. Postaje težak kada svaka nova funkcionalnost mora razumjeti unutarnju strukturu triju drugih servisa, dviju tablica i potrošača reda poruka kojeg nitko ne želi dirati. Rezultat se i dalje može nazivati arhitekturom mikroservisa, ali se ponaša kao distribuirani monolit: redoslijed implementacije je važan, male se promjene široko šire, a kvarovi putuju dalje nego što se očekuje.

Razdvajanje nije apstraktna arhitektonska vrlina. To je praktičan način da promjene budu jeftinije, kvarovi ograničeniji, a odgovornosti jasnije. Cilj nije ukloniti ovisnosti. Cilj je krhke ovisnosti o implementaciji zamijeniti promišljenim, stabilnim ugovorima.

Zašto zajednički interni dijelovi stvaraju krhke sustave

Razmotrite servis za narudžbe kojem trebaju podaci o kupcima. Najbrži put često je izravno povezivanje s bazom podataka kupaca ili uvoz ORM modela servisa za kupce. To odmah radi, ali servis za narudžbe čini ovisnim o detaljima koji nikada nisu dizajnirani kao javni ugovor: nazivima tablica, semantici stupaca, migracijama, događajima modela i konfiguraciji okvira.

Kada se to dogodi, naizgled bezopasna promjena sheme kupaca može pokvariti stvaranje narudžbi. Migracija baze podataka sada zahtijeva koordinaciju između timova. Lokalno razvojno okruženje treba dodatne vjerodajnice i sheme. Servis može izgledati zdravo, a pritom otkazivati zato što se tablica koju ne posjeduje promijenila ispod njega.

Isti se problem pojavljuje kod povezanosti na razini koda. Dijeljenje PHP paketa koji sadrži domenske entitete može zvučati uredno, ali često znači da jedan omeđeni kontekst prenosi svoj rječnik i pravila u drugi. Objekt Customer koristan za upravljanje identitetom nije automatski pravi prikaz za naplatu, ispunjenje narudžbe ili podršku.

Dizajnirajte API-je oko mogućnosti, a ne pohrane

Robustan API potrošačima govori što mogu zatražiti od servisa. Ne bi trebao izlagati način na koji servis danas pohranjuje svoje podatke. Umjesto da drugom servisu dopustite upit nad tablicom customers, izložite mogućnost kao što je „dohvati podobnost kupca za naplatu” ili „rezerviraj zalihe za narudžbu”.

Ta je razlika važna jer mogućnost može ostati stabilna dok se njezina implementacija mijenja. Servis za kupce može prijeći s jedne baze podataka na drugu, podijeliti polje adrese, dodati predmemoriranje ili pozvati vanjskog pružatelja provjere. Potrošači to ne bi trebali znati.

Namjerno zadržite male korisne terete zahtjeva i odgovora. Vraćanje svakog polja iz internog zapisa stvara slučajno obećanje da će svako polje ostati smisleno i dostupno. Vratite samo ono što je potrošaču potrebno za odluku.

final class CheckoutCustomerResponse
{
    public function __construct(
        public readonly string $customerId,
        public readonly bool $canPlaceOrder,
        public readonly string $displayName,
    ) {
    }
}

Takva je vrsta odgovora trenutačno manje fleksibilna, ali je s vremenom trajnija. Izražava slučaj uporabe umjesto da odražava redak.

Učinite ugovor eksplicitnim

HTTP krajnja točka više je od rute i JSON korisnog tereta. Njezin ugovor uključuje autentikaciju, validaciju, statusne kodove, idempotentnost, paginaciju kada je relevantna, vremenska ograničenja i semantiku neuspjeha. Ako se te odluke ostave implicitnima, klijenti izmišljaju vlastite pretpostavke.

Na primjer, krajnja točka za stvaranje narudžbe trebala bi razlikovati nevaljan zahtjev od prolaznog problema ovisnosti. Neuspjeh validacije ne bi trebalo ponavljati; nedostupni nizvodni servis možda bi trebalo. Odgovor bi tu razliku trebao jasno pokazati.

POST /orders
Idempotency-Key: 8ac4f55e-7a97-4cc0-b4dc-c37d7386b49a

HTTP/1.1 201 Created
Content-Type: application/json

{
  "orderId": "ord_123",
  "status": "pending"
}

Ako se isti ključ idempotentnosti ponovno pošalje nakon mrežnog vremenskog ograničenja, servis bi trebao vratiti izvorni ishod umjesto stvoriti drugu narudžbu. To nije kozmetička značajka API-ja. Neophodna je kada pozivatelji ponavljaju pokušaj nakon neizvjesnih neuspjeha.

Verzionirajte suzdržano

Verzioniranje nije izgovor za nepažljive lomljive promjene. Nova verzija API-ja prikladna je kada se semantika doista mora promijeniti, ali mnoge su promjene sigurno aditivne: uvedite neobavezno polje, dodajte novu krajnju točku ili podržite novu vrijednost enuma samo kada su klijenti spremni njome rukovati.

  • Dokumentirajte obavezna polja, neobavezna polja i zadano ponašanje.
  • Definirajte koje odgovore na pogreške klijenti mogu primiti i koji se mogu ponoviti.
  • Namjerno zastarijevajte, uz put migracije i poznat datum uklanjanja.
  • Testirajte ugovore neovisno o detaljima implementacije.

Koristite asinkrone granice za rad koji ne treba trenutačan odgovor

Ne pripada svaka interakcija putanji zahtjeva. Slanje potvrde, ažuriranje analitike, obavještavanje skladišta ili sinkronizacija indeksa pretraživanja obično ne trebaju završiti prije nego što API može potvrditi da je narudžba prihvaćena.

Događaji mogu smanjiti vremensku povezanost: servis za narudžbe bilježi promjenu stanja, zatim objavljuje događaj kao što je order.created. Potrošači reagiraju u vlastito vrijeme. To svakom servisu daje veću kontrolu nad dostupnošću i implementacijom.

Ali asinkroni sustavi jednu vrstu složenosti mijenjaju drugom. Poruke mogu stići više puta, stići kasno ili biti obrađene nakon drugog povezanog događaja. Potrošači bi trebali biti idempotentni, događaji bi trebali sadržavati identifikatore i koristan kontekst, a rukovatelji bi trebali tolerirati ponovnu reprodukciju. „Točno jednom” nije sigurna zadana pretpostavka za distribuirani tijek rada.

Za servise koji se oslanjaju na bazu podataka, obrazac transakcijskog outboxa često je pragmatična zaštita. Pohranite promjenu domene i odlazni događaj u istoj transakciji baze podataka, a zatim zasebno objavite outbox zapis. Time se sprječava čest neuspjeh u kojem je potvrda promjene u bazi uspješna, ali objava događaja ne uspije, zbog čega drugi servisi nisu svjesni promjene.

Neka svaki servis bude odgovoran za vlastite podatke

Vlasništvo nad podacima mjesto je gdje razdvajanje postaje stvarno. Servis bi trebao biti autoritet za podatke i pravila u svojoj domeni. Drugi servisi mogu čuvati lokalne projekcije ili snimke stanja, ali ne bi trebali izravno zapisivati u tablice tog servisa.

Modeli za čitanje mogu biti osobito korisni za performanse. Umjesto nekoliko sinkronih poziva tijekom svakog zahtjeva, servis može održavati mali podskup vanjskih podataka koji mu je potreban. To može značiti prihvaćanje eventualne konzistentnosti, pa poslovanje mora odlučiti gdje je malo zastarjela vrijednost prihvatljiva, a gdje je potrebna odluka u stvarnom vremenu.

Docker i lokalna orkestracija ne mijenjaju to načelo. Zajednički docker-compose.yml može razvoj učiniti praktičnim, ali ne bi smio potajno postati razlog da svaki servis dijeli baze podataka, vjerodajnice ili životne cikluse implementacije. Alati za praktičnost trebali bi podržavati granice, a ne brisati ih.

Gradite za nesavršene mreže

Poziv metode unutar jednog PHP procesa predvidljiv je. Mrežni poziv nije. Može biti spor, djelomično neuspjeti, vratiti se nakon što je pozivatelju isteklo vrijeme ili uspjeti dok se odgovor gubi. Prema svakoj udaljenoj ovisnosti postupajte u skladu s tim.

  • Postavite eksplicitna vremenska ograničenja povezivanja i zahtjeva.
  • Ponavljajte samo prolazne neuspjehe te koristite ograničeni broj ponavljanja s postupnim čekanjem.
  • Izbjegavajte automatska ponavljanja za ne-idempotentne operacije, osim ako postoji mehanizam idempotentnosti.
  • Koristite prekidanje kruga ili postupnu degradaciju kada je ovisnost opetovano nedostupna.
  • Bilježite korelacijske identifikatore i mjerite latenciju ovisnosti odvojeno od latencije aplikacije.

Te prakse nisu samo obrambeno programiranje. Sprječavaju da jedan nezdrav servis potroši svakog PHP radnika, popuni skupove veza i lokalizirani incident pretvori u širi prekid rada.

Razdvajanje je disciplina jasnih obećanja

Najjače granice servisa ne nastaju odabirom REST-a, redova poruka, Dockera ili određenog PHP okvira. One proizlaze iz odluke o tome što servis obećava, što posjeduje i na što se njegovi potrošači smiju osloniti.

Započnite s ovisnošću koja uzrokuje najviše problema s koordinacijom. Zamijenite izravni pristup tablici uskom mogućnošću. Definirajte ponašanje pri neuspjehu. Dodajte idempotentnost gdje su ponovni pokušaji mogući. Premjestite nebitan rad izvan kritične putanje. Svaka promjena čini sustav malo manje ovisnim o sinkroniziranom znanju.

Robusni API-ji čine više od povezivanja servisa. Timovima daju prostor da se razvijaju neovisno, što je stvarna korist arhitekture koja traje.

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.