Napuštanje monolita: pragmatičan put prema skalabilnim sustavima
Monolit nije neuspjeh. Često je najbrži način da korisnu ideju pretvorite u funkcionalan proizvod: jedan repozitorij, jedna implementacija, jedna baza podataka i jednostavan mentalni model. Problem nastaje kada rast sustava nadmaši sposobnost tima da ga sigurno mijenja.
U tom trenutku „prijeđite na mikroservise” može zvučati kao očit odgovor. Nije. Dijeljenje baze koda uvodi mrežne kvarove, distribuirane podatke, složenije otklanjanje pogrešaka, više implementacija i nove operativne odgovornosti. Pragmatični cilj nije napustiti monolit iz načela. Cilj je smanjiti trošak i rizik promjena uz očuvanje pouzdanosti.
Prepoznajte stvarne razloge za podjelu
Velika aplikacija može dugo ostati zdrava. Broj datoteka, veličina repozitorija ili entuzijazam tima za Kubernetes slabi su razlozi za njezinu dekompoziciju. Umjesto toga, tražite trajne granice koje stvaraju konkretne probleme s isporukom ili skaliranjem.
- Podsustav treba neovisno skaliranje, poput obrade slika, generiranja izvješća ili isporuke webhookova.
- Promjene u jednom području opetovano zahtijevaju rizična izdanja u nepovezanim područjima.
- Domena ima drukčije zahtjeve za dostupnost, sigurnost ili zadržavanje podataka.
- Timovi ne mogu raditi neovisno jer su vlasništvo i koordinacija izdanja stalno isprepleteni.
- Opterećenje narušava odzivnost puta zahtjeva okrenutog korisnicima.
Ovi signali upućuju na granicu s operativnom vrijednošću. Ne i nejasna želja za „modernizacijom”. Ako je glavni problem zamršena baza koda, počnite poboljšavanjem njezine unutarnje strukture. Dobro modulariziran monolit često je najbolja platforma iz koje se kasnije mogu izdvojiti servisi.
Učinite monolit modularnim prije izdvajanja bilo čega
U PHP pozadinskom sustavu to obično znači organizirati kod prema poslovnim sposobnostima, a ne samo prema tehničkim slojevima. Umjesto da svakom kontroleru, naredbi i modelu dopustite pristup svakoj tablici i prostoru imena, uspostavite module poput naplate, identiteta, kataloga ili ispunjenja narudžbi. Svaki modul trebao bi izlagati promišljene operacije na razini aplikacije i držati detalje svoje pohrane privatnima.
Na primjer, modul narudžbi može pružati operaciju koja kreira narudžbu i emitira događaj domene. Drugi moduli ne bi trebali izravno ažurirati tablice narudžbi samo zato što je to praktično. Ta je disciplina vrijedna čak i ako se sve i dalje izvodi u jednoj PHP aplikaciji i jednoj bazi podataka.
final class PlaceOrder
{
public function __construct(
private OrderRepository $orders,
private EventPublisher $events,
) {
}
public function handle(PlaceOrderRequest $request): Order
{
$order = Order::create($request->customerId, $request->items);
$this->orders->save($order);
$this->events->publish(new OrderPlaced($order->id()));
return $order;
}
}
Primjer je namjerno jednostavan. U produkciji razmotrite granice transakcija, validaciju, idempotentnost i način pohrane događaja. Važno je da modul posjeduje tijek rada. To vlasništvo postaje ugovor za budući servis, umjesto slučajne zbirke tablica.
Izdvojite sposobnost, a ne tehnički sloj
Dobri prvi kandidati obično su uske, asinkrone ili resursno zahtjevne sposobnosti: indeksiranje pretraživanja, obavijesti, pretvorba dokumenata, izvoz revizijskih podataka ili integracija pružatelja platnih usluga. Imaju jasne ulaze i izlaze, a njihovi se kvarovi često mogu ponoviti bez blokiranja korisničkog zahtjeva.
Loš prvi kandidat za izdvajanje jest ključni poslovni tijek rada koji dotiče svaki dio sustava. To će možda na kraju biti potrebno, ali istodobno povećava svaki problem distribuiranih sustava: vlasništvo nad podacima, konzistentnost transakcija, latenciju, autentifikaciju, verzioniranje i odgovor na incidente.
Definirajte ugovor prije implementacije
Počnite s interakcijom, a ne s okvirom. Odlučite treba li novoj komponenti sinkroni API, asinkrona poruka ili oboje. API zahtjev–odgovor prikladan je kada pozivatelj treba trenutačan odgovor. Red poruka obično je bolji kada se posao može obaviti kasnije i treba preživjeti privremene kvarove.
Za vanjske API-je ili API-je između servisa jasno definirajte ponašanje pri kvarovima. Istjeci vremena, ponovni pokušaji, duplicirani zahtjevi i djelomični prekidi rada uobičajena su stanja. Zahtjev za plaćanje, na primjer, trebao bi sadržavati ključ idempotentnosti kako ponovni pokušaj ne bi stvorio drugu naplatu. Slijepo ponavljanje svake pogreške nije otpornost; može pretvoriti prekid rada u poplavu dupliciranog posla.
$response = $client->post('/payments', [
'headers' => [
'Idempotency-Key' => $idempotencyKey,
],
'json' => $payload,
'timeout' => 3.0,
]);
Istek vremena pozivatelju govori samo da odgovor nije stigao na vrijeme. Ne dokazuje da udaljeni sustav nije učinio ništa. Dizajnirajte API i postupak usklađivanja oko te neizvjesnosti.
Pažljivo premjestite vlasništvo nad podacima
Najčešći arhitektonski prečac ujedno je i jedan od najštetnijih: nekoliko servisa čita i zapisuje iste tablice baze podataka. Djeluje učinkovito jer podatke ne treba premještati, ali zadržava staru povezanost skrivajući je iza zasebnih implementacija.
Svaki izdvojeni servis trebao bi postati autoritativni vlasnik svojih podataka. Drugi servisi komuniciraju putem API-ja ili događaja te prema potrebi održavaju vlastite modele za čitanje. To može uvesti eventualnu konzistentnost, što nije automatski nedostatak. Jednostavno zahtijeva dizajn svjestan proizvoda: prikažite stanje na čekanju, pružite jasna ažuriranja statusa i izbjegavajte obećavati trenutačne rezultate kada je tijek rada asinkron.
Migracija bi trebala biti postupna. Najprije usmjerite nove zapise kroz granicu modula. Zatim uspostavite pouzdan način sinkronizacije podataka, popunjavanja povijesnih zapisa i provjere brojeva i ponašanja. Tek nakon što se novi vlasnik dokaže, stari put zapisa trebao bi nestati. Privremeni sloj kompatibilnosti često je sigurniji od jednog prijelaza sve ili ništa.
Rano izgradite operativne temelje
Servisi nisu samo manje aplikacije. Oni su aplikacije kojima se upravlja neovisno. Prije izdvajanja većeg broja njih, uspostavite ponovljivu osnovu za konfiguraciju, zapisivanje, zdravstvene provjere, metrike, implementaciju i vraćanje na prethodnu verziju.
Docker može učiniti lokalna okruženja i implementacije dosljednijima, ali spremnik nije operativna strategija. PHP servis i dalje treba predvidivo ponašanje pri pokretanju, konfiguraciju temeljenu na okruženju, strukturirane zapise i jasnu razliku između spremnosti i živosti. Spremnost odgovara na pitanje može li sada posluživati promet; živost odgovara na pitanje treba li ga ponovno pokrenuti.
Također, učinite vidljivost dijelom prvog izdanja. Kada zahtjev prolazi kroz API pristupnik, PHP aplikaciju, radnik reda poruka i novi servis, zapisi bez identifikatora korelacije postaju potraga za blagom. Prenosite ID zahtjeva ili praćenja preko granica, bilježite smislene pogreške i pratite važne poslovne ishode: neuspjele poslove, odgođenu obradu, povišene stope pogrešaka i zaostatak u redu.
Zadržite sustav lakšim za promjene
Najbolja arhitektura nije ona s najviše servisa. To je ona koja timu omogućuje da razumije promjenu, testira je, implementira je i oporavi se od pogreške bez herojske koordinacije. Ponekad to znači jednu modularnu aplikaciju. Ponekad znači nekoliko fokusiranih servisa oko jasnih poslovnih granica.
Dekompozicija djeluje kada slijedi stvarni pritisak, jača vlasništvo i opravdava svoju operativnu složenost. Počnite tako da granice učinite eksplicitnima unutar monolita. Izdvojite jednu sposobnost s ugovorom koji možete testirati i promatrati. Učite iz rezultata prije umnožavanja servisa.
Monolit nije nešto što treba odbaciti dramatičnim ponovnim pisanjem. To je sustav koji treba razvijati promišljeno. Kada se dobro izvede, put do skaliranja nije skok u distribuiranu složenost. To je niz malih odluka koje sljedeću promjenu čine sigurnijom od prethodne.