Izvan CRUD-a: Arhitektura API-ja s jasnom svrhom i dugovječnošću
CRUD je mjesto gdje mnogi API-ji počinju i gdje ih previše njih ostaje. Stvori, pročitaj, ažuriraj, izbriši: obrazac je lako objasniti, brzo ga je moguće postaviti i često je dovoljan za interni prototip. No API koji opstaje uz stvarne korisnike, promjenjive zahtjeve, neuspjele zahtjeve, pozadinske poslove i nekoliko klijentskih aplikacija treba snažnije težište od tablica baze podataka.
Trajno pitanje nije „Koje krajnje točke izlažu ovaj model?” Ono glasi: „Koje mogućnosti ovaj sustav treba pružati i koja pravila moraju ostati istinita dok ih pruža?” Taj zaokret pretvara API iz tankog transportnog sloja u promišljenu granicu oko poslovnog ponašanja.
Dizajnirajte prema namjeri, a ne prema tablicama
API usmjeren na resurse vrijedan je, ali nazivi resursa ne bi trebali svaku operaciju prisiljavati na generička ažuriranja polja. Razmotrite narudžbu. Naivna krajnja točka može klijentu dopustiti slanje PATCH /orders/42 s novim status. To izgleda fleksibilno dok sustav ne mora spriječiti otpremu prije plaćanja, rezervirati zalihe, obavijestiti skladište i zabilježiti revizijski trag.
Status nije samo stupac. On predstavlja prijelaz s posljedicama. Učinite tu namjeru vidljivom:
POST /orders/42/confirm
POST /orders/42/cancel
POST /orders/42/ship
Te krajnje točke nisu izgovor za napuštanje dosljednog dizajna API-ja. One priznaju da su neke promjene stanja radnje domene. Svaka radnja može provjeriti preduvjete, gdje je moguće atomski obaviti posao, emitirati događaj i vratiti pogrešku koja klijentu govori što može učiniti sljedeće.
Slobodno koristite CRUD za jednostavne podatke: postavke, skice, referentne zapise ili adresar kojim upravlja korisnik. Uvedite eksplicitne naredbe kada ažuriranje nosi pravila, nuspojave ili značajan prijelaz životnog ciklusa.
Neka invarijante žive na jednom mjestu
Najlakši način za stvaranje krhkog pozadinskog sustava jest raspršiti poslovna pravila po kontrolerima, validatorima zahtjeva, radnicima u redu čekanja i frontend kodu. Kontroler provjerava jedan uvjet, skripta za uvoz ga preskače, a nova administratorska krajnja točka neprimjetno krši pretpostavku koja nikada nije bila zapisana.
U PHP aplikaciji kontroleri bi obično trebali prevoditi HTTP u poziv aplikacije. Ne bi trebali postati jedino mjesto gdje sustav zna kako potvrditi narudžbu ili dodijeliti pretplatu. Smjestite operaciju iza servisa, radnje ili slučaja upotrebe usmjerenog na domenu čiji naziv odražava ponašanje.
final class ConfirmOrder
{
public function handle(Order $order): void
{
if (! $order->canBeConfirmed()) {
throw new OrderCannotBeConfirmed();
}
DB::transaction(function () use ($order) {
$order->confirm();
$this->inventory->reserveFor($order);
$this->events->dispatch(new OrderConfirmed($order->id));
});
}
}
Točna struktura okvira manje je važna od granice. Ista operacija trebala bi biti upotrebljiva iz HTTP kontrolera, CLI naredbe ili potrošača poruka bez dupliciranja pravila. Ograničenja baze podataka i dalje su važna: aplikacijski kod pruža jasno ponašanje, dok jedinstveni indeksi, strani ključevi i prikladne provjere štite podatke kada se aplikacijski kod zaobiđe ili se istodobni zahtjevi sudare.
Odaberite ugovore koji se mogu razvijati
API ugovor je površina proizvoda. Kada klijenti jednom ovise o njemu, promjena polja ili oblika odgovora ima cijenu. Praktičan odgovor nije beskonačno verzioniranje; to je da aditivna promjena bude zadana opcija.
- Dodajte neobavezna polja odgovora umjesto promjene značenja postojećih.
- Prihvaćajte samo dokumentirana ulazna polja te odbijte ili namjerno zanemarite nepoznata polja prema jasnim pravilima.
- Koristite stabilne identifikatore koji ne otkrivaju pretpostavke o unutarnjoj pohrani.
- Vraćajte predvidljive oblike pogrešaka kako bi klijenti mogli razlikovati neuspjehe validacije, neuspjehe autorizacije, sukobe i privremene kvarove.
- Paginirajte krajnje točke kolekcija od početka kada kolekcija može rasti.
Verzionirajte samo kada je promjena uistinu nekompatibilna i ne može se uvesti uz postojeće ponašanje. Broj verzije ne rješava dvosmislenost. Jasna semantika, razdoblja zastarijevanja i testovi ugovora čine više za očuvanje povjerenja.
Idempotentnost je dizajnerski izbor
Mreže ponavljaju pokušaje. Korisnici dvaput kliknu. Pokretači poslova ponovno se pokreću. Krajnja točka koja stvara plaćanje, narudžbu ili zahtjev za dodjelu resursa ne bi trebala pretpostaviti da će biti pozvana točno jednom.
Za operacije kod kojih je dvostruko izvršavanje štetno, prihvatite ključ idempotentnosti i trajno pohranite povezanost između tog ključa, pozivatelja, zahtjeva i dovršenog rezultata. Ponovni pokušaj tada može primiti izvorni rezultat umjesto da ponovi nuspojavu. Dizajn pohrane mora obraditi istodobnu upotrebu istog ključa; jedinstveno ograničenje baze podataka pouzdanije je od zaključavanja na razini procesa.
Idempotentnost ne znači da je svaki zahtjev sigurno ponoviti. Znači da API definira što ponavljanje znači i dosljedno provodi tu definiciju.
Neka baza podataka provodi stvarnost
Performanse i ispravnost često se susreću u modelu podataka. Indeksirajte stupce koji se koriste za spajanja, selektivne filtre, sortiranje i ključeve za pretraživanje, ali nemojte dodavati indekse refleksno: svaki indeks povećava trošak pisanja i zauzeće pohrane. Pregledajte upite koje API stvarno izvršava, a zatim mjerite s reprezentativnim podacima.
Čest obrazac neuspjeha jest učitavanje stranice nadređenih zapisa, a zatim upit povezanih podataka jednom po zapisu. Kod je čitljiv zasebno, ali zahtjev postaje sporiji kako stranica raste. Koristite unaprijed učitavanje ili ciljano spajanje kada je odnos potreban te odaberite samo polja potrebna odgovoru.
Transakcije trebaju štititi koherentnu jedinicu rada, a ne obuhvaćati proizvoljno spor rad. Izbjegavajte pozivanje vanjskih usluga dok je transakcija baze podataka otvorena. Najprije potvrdite trajno stanje, a zatim pošaljite posao ili objavite događaj kroz pouzdan obrazac prikladan za sustav. Ako naknadna radnja ne uspije, potrebna joj je strategija ponovnog pokušaja i uočljiv put neuspjeha; pretvaranje da se dogodila gore je od prijavljivanja.
Neka HTTP rad bude kratak, a pozadinski rad eksplicitan
API zahtjev bi općenito trebao validirati ulaz, autorizirati pozivatelja, napraviti potrebnu promjenu stanja i brzo vratiti odgovor. Slanje velike skupine e-poruka, generiranje izvještaja, promjena veličine medija ili sinkronizacija sustava treće strane pripadaju pozadinskoj obradi.
To razdvajanje pomaže samo ako su poslovi dizajnirani jednako ozbiljno kao krajnje točke. Posao treba jasan teret, sigurno ponašanje pri ponovnim pokušajima, pravilo o najvećem broju ponovnih pokušaja i dovoljno konteksta za zapisnike i alate podrške. Trebao bi biti siguran za izvođenje nakon rušenja procesa. Ako se ne može učiniti idempotentnim, zaštitite nepovratni korak trajnim prijelazom stanja.
Docker može učiniti lokalna i produkcijska okruženja ponovljivijima, ali ne uklanja operativne odluke. Konfiguraciju aplikacije držite u varijablama okruženja, migracije pokrećite kao namjeran korak implementacije i neka provjere ispravnosti odražavaju ovisnosti potrebne za posluživanje prometa. Uspješno pokretanje spremnika nije dokaz da aplikacija može obrađivati zahtjeve.
Promatranje je dio sučelja
Kada API ne uspije, klijent treba koristan odgovor, a operater koristan trag. U zapisnike i odgovore uključite identifikator zahtjeva ili korelacije. Bilježite strukturirani kontekst poput rute, autentificiranog aktera gdje je prikladno, naziva operacije i kategorije iznimke. Nemojte bilježiti vjerodajnice, tokene ili osjetljiva tijela zahtjeva samo zato što su praktični tijekom otklanjanja pogrešaka.
Metrike trebaju odgovoriti na operativna pitanja: Koliko zahtjeva ne uspijeva? Koje su krajnje točke spore? Rastu li redovi čekanja? Uspijevaju li ponovni pokušaji ili se gomilaju? Zapisnici objašnjavaju pojedinačne incidente; metrike otkrivaju obrasce. Oboje su najvrjedniji kada se dodaju prije prvog teškog produkcijskog incidenta.
Gradite za sljedeću promjenu
Dugovječnost se ne postiže predviđanjem svake buduće značajke. Dolazi od toga da današnja pravila budu eksplicitna, da odgovornosti ostanu uske i da se sačuvaju mogućnosti ondje gdje je promjena vjerojatna. Dobro dizajniran API ne izlaže svaku operaciju baze podataka. On komunicira mogućnosti, štiti invarijante i klijentima pruža ponašanje na koje se mogu osloniti.
CRUD ostaje koristan. Jednostavno nije arhitektura. Arhitektura počinje kada API prestane zrcaliti tablice i počne izražavati obećanja koja sustav namjerava ispuniti.