Prestanite prepisivati API-je: projektirajte za nepromjenjivu pozadinsku logiku
Većina preinaka API-ja započinje razumnim zahtjevom: novom mobilnom aplikacijom, integracijom s partnerom, redizajniranim naplatnim procesom ili bržim zaslonom za izvještavanje. Pogreška je taj novi zahtjev tretirati kao dokaz da poslovnu logiku pozadinskog sustava treba ponovno izgraditi.
API je mehanizam isporuke. Vaša pravila za određivanje cijena, dozvole, zalihe, naplatu i prijelaze stanja predstavljaju poslovanje. Kada su ta pravila isprepletena s kontrolerima, formatima zahtjeva, ORM upitima i serijalizacijom odgovora, svaki novi klijent postaje izgovor za ponovno pisanje koda koji je trebao ostati stabilan.
Cilj nije zauvijek zamrznuti sustav. Cilj je projektirati pozadinski sustav tako da se promjene događaju na rubovima, dok temeljna logika ostaje razumljiva, testabilna i namjerno jednostavna.
Odvojite promjenjivi API od stabilnih poslovnih pravila
Korisna granica je jednostavna: HTTP pitanja pripadaju izvan jezgre aplikacije. Kontroleri trebaju prevesti zahtjev u ulaz koji aplikacija razumije, pozvati slučaj uporabe i prevesti rezultat u odgovor. Ne bi trebali odlučivati može li se narudžba otkazati niti izračunavati popust skriven unutar JSON tereta.
U PHP-u to može izgledati ovako:
final class CancelOrderController
{
public function __invoke(
ServerRequestInterface $request,
CancelOrder $cancelOrder
): ResponseInterface {
$orderId = (string) $request->getAttribute('orderId');
$actorId = (string) $request->getAttribute('actorId');
$result = $cancelOrder->handle(
new CancelOrderCommand($orderId, $actorId)
);
return new JsonResponse([
'orderId' => $result->orderId,
'status' => $result->status,
]);
}
}
Kontroler poznaje HTTP. Slučaj uporabe CancelOrder poznaje operaciju. Pravila domene tada se mogu ponovno upotrijebiti putem REST krajnje točke, zadatka naredbenog retka, potrošača reda poruka ili budućeg GraphQL razrješivača bez kopiranja pravila otkazivanja.
Ova razlika također čini verzioniranje API-ja manje zastrašujućim. Verzijska krajnja točka može prihvaćati različite nazive polja ili vraćati drugačiji oblik odgovora, dok obje verzije pozivaju isti slučaj uporabe aplikacije. Verziju ugovora po potrebi; ne verzionirajte poslovanje samo zato što se promijenio prikaz.
Oblikujte oko slučajeva uporabe, a ne tablica baze podataka
API-ji oblikovani poput tablica primamljivi su jer ih je brzo izložiti: izradite krajnju točku za svaki model, prihvatite koje god stupce postoje i prepustite klijentu sastavljanje tijekova rada. Takav pristup pretvara shemu baze podataka u javnu odluku o proizvodu.
Umjesto toga, modelirajte smislene radnje. POST /orders/{id}/cancel jasnije komunicira namjeru od generičke krajnje točke za ažuriranje koja prihvaća {"status":"cancelled"}. Izričita radnja daje poslužitelju jedno mjesto za provjeru dozvola, provjeru stanja, oslobađanje rezervacija i pokretanje naknadnog rada.
Tablice baze podataka trebaju podržavati domenu, a ne diktirati je. API okrenut korisnicima može vratiti sažetak narudžbe sastavljen iz nekoliko tablica. Suprotno tome, interna tablica može sadržavati polja za reviziju, implementacijske zastavice ili prijelazne stupce koji nikada ne bi smjeli prijeći granicu API-ja.
Održavajte ugovore izričitima
Stabilna logika pozadinskog sustava treba izričite ulaze i izlaze. Izbjegavajte prosljeđivanje neobrađenih nizova zahtjeva duboko u aplikaciju, gdje se neobavezna polja i pretpostavke specifične za transport nekontrolirano šire. Koristite objekte naredbi, vrijednosne objekte i imenovane tipove rezultata tamo gdje donose jasnoću.
- Provjerite sintaksu i obavezna polja na granici.
- Provjerite poslovna pravila u slučaju uporabe ili modelu domene.
- Vraćajte ishode na razini aplikacije, a ne objekte odgovora okvira.
- Preslikajte neuspjehe domene na HTTP statusne kodove na granici API-ja.
Primjerice, „narudžba nije pronađena” i „narudžba se ne može otkazati nakon otpreme” različiti su ishodi aplikacije, čak i ako ih API odluči prikazati drukčije. Zadržavanje te razlike čuva korisno ponašanje za svako sučelje koje poziva slučaj uporabe.
Usmjerite ovisnosti prema unutra
Logika pozadinskog sustava postaje krhka kada izravno ovisi o određenom upravljačkom programu baze podataka, biblioteci za redove poruka, klijentu predmemorije ili modelu okvira. Ti su alati vrijedni, ali trebaju se nalaziti iza sučelja u vlasništvu aplikacije.
Slučaj uporabe može ovisiti o OrderRepository i TransactionManager, dok ih infrastrukturni sloj implementira pomoću PostgreSQL-a i PHP okvira koji se već koristi. Jezgra treba izraziti što joj treba, a ne kako to izvodi određeni adapter.
Ovo nije zahtjev za složenom apstrakcijom oko svake biblioteke. Sučelje s jednom metodom, stvoreno samo da sakrije stabilan uslužni program, dodaje ceremonijalnost bez dobivanja fleksibilnosti. Uvedite granicu ondje gdje ovisnost utječe na poslovno ponašanje, testiranje, implementaciju ili trošak zamjene. Pohrana podataka, pružatelji plaćanja, isporuka e-pošte i vanjski API-ji uobičajeni su primjeri.
Transakcije zaslužuju posebnu pažnju. Ako otkazivanje narudžbe mijenja njezino stanje i oslobađa zalihe, te promjene trebaju biti koordinirane u jednoj transakcijskoj operaciji tamo gdje baza podataka to podržava. Ako operacija također mora objaviti događaj, nemojte pretpostaviti da su potvrda baze podataka i objava posredniku poruka jedna atomska radnja. Zapis u outboxu pohranjen u istoj transakciji baze podataka često je praktičan način bilježenja rada koji se poslije može pouzdano isporučiti.
Koristite migracije kao evoluciju, a ne prekid
Promjene sheme čest su razlog zbog kojeg se timovi osjećaju prisiljenima na preinaku. Sigurniji pristup je uvođenje kompatibilnih promjena u fazama: dodajte novu pohranu, po potrebi zapisujte u oba prikaza, popunite postojeće podatke, prebacite čitanja, a zatim uklonite stari put tek nakon što se više ne koristi.
To je važno u Docker implementacijama jer se aplikacijski spremnici mogu zamjenjivati neovisno tijekom uvođenja. Implementacija koja zahtijeva da svaki spremnik pokrene novi kod u točno istom trenutku krhka je. Dajte prednost razdoblju u kojem stare i nove verzije aplikacije mogu raditi s istom shemom.
Migracije baze podataka trebaju se pokretati kroz kontrolirani korak implementacije, a ne automatski iz svakog aplikacijskog spremnika pri pokretanju. Više replika koje se natječu u primjeni iste migracije operativni je problem, a ne arhitektonska strategija. Proces implementacije također treba jasno prikazati neuspjeh i zaustaviti se prije posluživanja koda koji zahtijeva migraciju koja nije dovršena.
Testirajte jezgru tamo gdje je najvažnije
Kada su poslovna pravila izolirana od HTTP-a i infrastrukture, najvažniji testovi postaju brzi jedinični testovi ili testovi aplikacije. Mogu izgraditi narudžbu, izvršiti slučaj uporabe i provjeriti ishod bez pokretanja web-poslužitelja ili cijelog Docker skupa.
To ne uklanja integracijske testove. I dalje su potrebni za upite repozitorija, migracije, međuprogram za autentikaciju, serijalizaciju i kritične vanjske granice. Ravnoteža je važna: upotrijebite manji broj realističnih integracijskih testova kako biste dokazali povezivanje, a veći broj usmjerenih testova kako biste zaštitili pravila.
Koristi ima i izvedba. Spori se krajnji priključci često pripisuju PHP-u ili okviru, dok su stvarni problem nekontrolirani obrasci upita, preveliki tereti odgovora, ponovljeni udaljeni pozivi ili indeksi koji nedostaju. Čista granica aplikacije olakšava pronalaženje tih troškova. Izmjerite stvarni put zahtjeva, pregledajte broj i trajanje upita te optimizirajte konkretno usko grlo umjesto da predmemorije raspršujete po kodnoj bazi.
Izgradite pozadinski sustav koji može prihvatiti zahtjeve
Nepromjenjiv pozadinski sustav ne znači netaknut pozadinski sustav. To znači da se središnje odluke mijenjaju polako jer su izražene u obliku koji podnosi nove klijente, nove krajnje točke, nove pojedinosti pohrane i nova pitanja implementacije.
Neka transportni kod bude tanak. Učinite poslovne radnje izričitima. Zaštitite bazu podataka da ne postane vaš javni API. Uvedite infrastrukturne granice tamo gdje smanjuju stvarni rizik i razvijajte sheme kroz kompatibilne korake.
Tada sljedeći zahtjev za „potpuno drukčijim API-jem” postaje ono što obično jest: novi adapter oko sustava čija je najvrjednija logika već na pravom mjestu.