Iznad mikroservisa: skaliranje API-ja s promjenjivim potrebama sustava
Mikrousluge se često predstavljaju kao prirodan sljedeći korak kada API počne rasti. Dolazi više prometa, timovi se umnažaju, implementacije postaju sporije, a monolit počinje djelovati kao ograničenje. Njegovo razdvajanje može pomoći — ali samo kada podjela odražava stvarne potrebe sustava.
Teška je istina da „mikrousluge” same po sebi nisu strategija skaliranja. One su razmjena: manje unutarnje povezanosti u zamjenu za mrežne granice, distribuirane podatke, operativno opterećenje i više načina otkazivanja. Zrela pozadinska arhitektura razvija se promišljenim donošenjem tih odluka, a ne tretiranjem broja usluga kao znaka napretka.
Skalirajte točku pritiska, a ne dijagram
API može imati poteškoća iz mnogih razloga: skupih upita prema bazi podataka, sporog ovisnog sustava treće strane, dugotrajnog izvoza, bučnog zakupnika, uskog grla pri implementaciji ili preopterećenog tima. Svi ti problemi ne zahtijevaju novu uslugu.
Počnite utvrđivanjem onoga što je zapravo pod pritiskom. Ako je krajnja točka kataloga proizvoda spora zato što spaja velike tablice i primjenjuje nekoliko filtara, izdvajanje „catalog-service” neće pojeftiniti upit. Možda će samo dodati HTTP poziv istom skupom upitu. Bolji prvi potezi mogu uključivati pregled indeksa, paginaciju, uži oblik odgovora, predmemoriranje ili model za čitanje dizajniran za taj obrazac pristupa.
Slično tome, ako generiranje PDF-a minutama drži PHP radnike zauzetima, korisna granica može biti asinkroni rad, a ne zasebna javna usluga. Stavite zahtjev u red čekanja, vratite identifikator zadatka i dopustite radniku da generira datoteku izvan životnog ciklusa zahtjeva. Sustav se razvio, ali još nije preuzeo trošak distribuirane domene proizvoda.
Zadržite modularni monolit dulje nego što se čini pomodnim
Dobro strukturiran monolit nije arhitektonski neuspjeh. Često je najbrži način za očuvanje transakcijske konzistentnosti, razumijevanje baze koda i isporučivanje promjena dok se domena još oblikuje.
„Modularan” je ključna riječ. PHP aplikacija može zadržati naplatu, identitet, naručivanje i obavijesti u istoj implementaciji, uz jasno vlasništvo u kodu. Izbjegavajte da svaki kontroler pristupa svakom modelu. Svakom modulu dajte granicu aplikacije, eksplicitna sučelja i ograničen skup odgovornosti za perzistenciju.
Na primjer, modul narudžbi trebao bi od modula određivanja cijena zatražiti cijenu putem definirane aplikacijske usluge ili ugovora. Ne bi trebao usputno reproducirati pravila određivanja cijena u SQL-u, kontroleru i pozadinskoj naredbi. Ta disciplina omogućuje buduće izdvajanje, ali i danas olakšava održavanje monolita.
Korisni signali da granica postaje stvarna
- Podsustav ima izrazito drukčiji profil skaliranja, poput obrade slika ili indeksiranja pretraživanja.
- Tim treba objavljivati i upravljati funkcionalnošću u neovisnom ritmu.
- Domena ima stabilno vlasništvo i jezik koji se razumije u cijeloj organizaciji.
- Podsustav može tolerirati asinkronu komunikaciju ili ima jasan ugovor za sinkrone pozive.
- Njegovi podaci mogu biti u neovisnom vlasništvu bez stalnih spajanja i transakcija preko granica.
Nijedan od ovih signala ne znači da je izdvajanje obavezno. Zajedno znatno olakšavaju obrazlaganje te odluke.
Granice baze podataka obično su stvarne granice
Lako je postaviti HTTP API ispred modula i nazvati ga mikrouslugom. Teško je pitanje posjeduje li svoje podatke. Ako više usluga izravno čita i zapisuje u iste tablice, arhitektura ima distribuiranu implementaciju bez distribuiranog vlasništva.
Dijeljene tablice stvaraju suptilnu povezanost. Migracija namijenjena jednoj usluzi može pokvariti drugu. Upit koji izgleda bezopasno može ovisiti o nedokumentiranom stupcu. Timovi ne mogu slobodno mijenjati sheme jer je svaki potrošač baze podataka sada dio plana objave.
Pri izdvajanju funkcionalnosti odlučite koja je usluga mjerodavna za svaki dio podataka. Druge usluge trebale bi dobiti potrebne informacije putem API-ja, događaja ili namjenski izrađenih repliciranih prikaza. To uvodi eventualnu konzistentnost, stoga ona mora biti osmišljena u korisničkom iskustvu i poslovnom procesu.
Razmotrite zalihe. API narudžbi može poslati zahtjev za rezervaciju, dok zalihe ostaju mjerodavne za dostupnu količinu. Usluga narudžbi ne bi trebala samostalno smanjivati retke zaliha. Ako je rezervacija asinkrona, narudžba može prijeći u stanje pending dok ne primi potvrdu ili neuspjeh. To je eksplicitnije od pretvaranja da je distribuirana operacija i dalje jedna transakcija baze podataka.
Dizajnirajte za neuspjeh prije dodavanja poziva usluga
Pozivi funkcija unutar procesa otkazuju predvidljivo. Mrežni pozivi ne. Mogu isteći, vratiti se sa zakašnjenjem, uspjeti nakon što je pozivatelj odustao ili otkazati dok udaljeni sustav još obrađuje zahtjev. Svaka nova sinkrona ovisnost mijenja karakteristike pouzdanosti krajnje točke.
Za svaki poziv između usluga definirajte praktične odgovore na nekoliko pitanja:
- Koje je vremensko ograničenje i je li kraće od preostalog proračuna vremena zahtjeva pozivatelja?
- Može li se operacija sigurno ponovno pokušati i kako se sprječava duplicirani rad?
- Što pozivatelj radi kada ovisnost nije dostupna?
- Koji se odgovor predmemorira, stavlja u red čekanja, degradira ili odbija?
- Kako će operateri povezati jedan klijentski zahtjev kroz više usluga?
Idempotentnost je posebno važna za naredbe. Zahtjev za plaćanje ili stvaranje narudžbe trebao bi sadržavati ključ idempotentnosti kako ponovno pokušavanje ne bi stvorilo drugu naplatu ili dupliciranu narudžbu. Ponovni pokušaji bez idempotentnosti nisu otpornost; oni su mehanizam za umnožavanje neuspjeha.
I događaji zahtijevaju pažnju. Pouzdano ih objavljujte, zadržite dovoljno konteksta za potrošače i učinite potrošače idempotentnima. Obradnik događaja može se pokrenuti više puta. Tretiranje duplicirane isporuke kao iznimne nemogućnosti način je na koji počinje odstupanje podataka.
Spremnici poboljšavaju pakiranje, a ne arhitekturu
Docker je vrijedan jer standardizira pakiranje izvršnog okruženja. PHP aplikacija, njezina proširenja i konfiguracija procesa mogu prolaziti kroz razvoj, testiranje i implementaciju u ponovljivom obliku. To je korisno bez obzira ima li sustav jednu aplikaciju koja se može implementirati ili njih dvadeset.
No spremnici ne uklanjaju potrebu za promatranjem sustava, upravljanjem konfiguracijom, rukovanjem tajnama, migracijama baze podataka ili planovima povratka na prethodnu verziju. Flota sićušnih spremnika s nejasnim vlasništvom teža je za upravljanje od pažljivo zapakiranog monolita.
Operativne aspekte zadržite konkretnima. Provjere stanja trebale bi odražavati može li instanca opslužiti namijenjeni promet. Zapisi bi trebali biti dovoljno strukturirani da povežu pogreške s identifikatorima zahtjeva. Metrike bi trebale razlikovati latenciju, stopu pogrešaka, zasićenje i dubinu reda čekanja. Migracije baze podataka trebale bi biti kompatibilne unatrag kada se stare i nove verzije aplikacije mogu izvršavati istodobno.
final class CreateOrderHandler
{
public function __invoke(CreateOrder $command): OrderId
{
$existing = $this->orders->findByIdempotencyKey($command->idempotencyKey());
if ($existing !== null) {
return $existing->id();
}
$order = Order::pending($command);
$this->orders->save($order);
$this->outbox->record(new OrderCreated($order->id()));
return $order->id();
}
}
Važan detalj nije točan raspored klasa. To je granica: zajedno trajno pohranite poslovnu promjenu i zapis događaja, a zatim objavite iz pouzdanog pozadinskog procesa. Time se izbjegava tvrdnja da je događaj poslan kada transakcija baze podataka kasnije ne uspije.
Neka arhitektura ostane reverzibilna
Najbolji put razvoja obično je postupan. Prvo poboljšajte granice modula. Zatim izolirajte skup rad iza redova čekanja. Uvedite namjenski model za čitanje ondje gdje to zahtijeva promet čitanja. Izdvojite jednu funkcionalnost kada su njezino vlasništvo, podaci i operativni zahtjevi jasni. Izmjerite rezultat prije ponavljanja obrasca.
Mikrousluge mogu biti izvrsno odredište za dijelove sustava. One su loš zadani identitet za cijeli sustav. Trajni je cilj jednostavniji: učiniti svaku promjenu razumljivom, svaku ovisnost namjernom i svaku odluku o skaliranju proporcionalnom pritisku koji rješava.
Tako API-ji rastu bez pretvaranja u labirint mrežnih poziva. Ne jureći za pomodnom arhitekturom, nego izgradnjom granica koje zaslužuju pravo na postojanje.