Razvoj

Decomposing Monoliths: A Pragmatic Path to Microservices

Razgradnja monolita: pragmatičan put do mikroservisa

Monolit nije neuspjeh. Često je najbrži način da se korisna ideja pretvori u funkcionalan proizvod: jedna implementacija, jedna baza koda, jedna baza podataka i manje pokretnih dijelova koje treba razumjeti. Problemi počinju kada aplikacija preraste granice koje njezina izvorna struktura može udobno podržati. Izdavanja postaju rizična, mala promjena zahtijeva poznavanje nepovezanih područja, a timovi troše više vremena na koordinaciju nego na isporuku.

Mikrousluge mogu pomoći, ali nisu lijek za svaku problematičnu bazu koda. One zamjenjuju složenost unutar procesa složenošću distribuiranih sustava: mrežnim kvarovima, verzioniranim ugovorima, asinkronim radnim tijekovima, vidljivošću sustava i operativnim opterećenjem. Pragmatičan cilj nije „razbiti monolit”. Cilj je poboljšati sposobnost sustava da se sigurno mijenja.

Počnite pronalaženjem točaka pritiska

Nemojte početi dijagramom tehnologija. Počnite dokazima. Koji se dijelovi aplikacije najčešće mijenjaju? Koja su izdavanja opasna? Gdje se odvojeni timovi međusobno blokiraju? Koja opterećenja trebaju neovisno skaliranje? Ti odgovori otkrivaju bi li granica usluge riješila stvarni problem ili samo stvorila novi.

U PHP aplikaciji tipična točka pritiska može biti tijek naplate koji obuhvaća cijene kataloga, zalihe, plaćanja, obavijesti i izvještavanje. Izdvajanje svih tih odgovornosti odjednom poziv je na stvaranje distribuiranog monolita. Bolji prvi kandidat obično je mogućnost s jasnom svrhom, ograničenim ovisnostima i timom koji je može preuzeti od početka do kraja.

Dobri rani kandidati često uključuju:

  • Generiranje i isporuku obavijesti.
  • Obradu konverzija medija ili dokumenata.
  • Indeksiranje pretraživanja i posluživanje upita.
  • Generiranje izvoza i drugi dugotrajni pozadinski rad.
  • Zasebnu integracijsku granicu, poput prilagodnika za pružatelja usluga plaćanja.

Ta područja obično imaju prirodno asinkron rad, mjerljivo opterećenje i manje razloga za dijeljenje transakcijskog stanja s ostatkom aplikacije.

Učinite monolit modularnim prije nego što išta izdvojite

Zamršeni monolit ne postaje čist samo zato što se njegove mape premjeste u zasebne repozitorije. Najprije stvorite granice unutar postojeće aplikacije. Grupirajte kod prema poslovnoj mogućnosti, a ne samo prema tehničkom sloju. Neka kontroleri budu tanki, smjestite radne tijekove aplikacije iza eksplicitnih sučelja i spriječite module da zadiru u modele postojanosti jedni drugih.

Na primjer, modul narudžbi ne bi smio izravno mijenjati tablice zaliha niti pozivati alat za slanje obavijesti e-poštom. Može objaviti namjeru poput OrderPlaced. Unutar monolita drugi modul može obraditi taj događaj u istom procesu. Kasnije se taj obrađivač može premjestiti iza reda čekanja bez prisiljavanja koda za naručivanje da uči pojedinosti o posredniku.

final class PlaceOrder
{
    public function __construct(
        private OrderRepository $orders,
        private EventPublisher $events,
    ) {
    }

    public function handle(PlaceOrderCommand $command): Order
    {
        $order = Order::place($command->customerId, $command->items);

        $this->orders->save($order);
        $this->events->publish(new OrderPlaced($order->id()));

        return $order;
    }
}

Točan okvir manje je važan od smjera ovisnosti. Poslovni kod trebao bi ovisiti o stabilnim apstrakcijama, dok HTTP klijenti, redovi čekanja, ORM modeli i SDK-ovi pružatelja usluga ostaju na rubovima. To čini izdvajanje promišljenom infrastrukturnom promjenom, a ne ponovnim pisanjem poslovnih pravila.

Izdvajajte prema mogućnosti, a ne prema tablici baze podataka

„Jedna usluga po entitetu” zvuči uredno, ali obično stvara brbljave API-je i krhke radne tijekove. Kupci, narudžbe, proizvodi i plaćanja podatkovni su koncepti; nisu automatski granice usluga. Korisna granica posjeduje poslovnu mogućnost, njezina pravila i podatke potrebne za provedbu tih pravila.

Nakon što usluga posjeduje podatke, druge usluge ne bi smjele izravno pisati u njezinu bazu podataka. Izravan pristup dijeljenoj bazi podataka stvara skrivenu povezanost: migracija sheme u jednom timu može tiho pokvariti produkcijsko opterećenje drugog tima. Umjesto toga izložite ugovor, bilo da je riječ o HTTP API-ju, toku poruka ili obome.

To ne znači da svaki zahtjev mora postati udaljen. Praktičan prijelaz često ostavlja glavnu aplikaciju i novoizdvojenu uslugu da privremeno dijele bazu podataka, uz jasno naveden rok i plan migracije. Takav aranžman tretirajte kao skelu. Ako postane trajan, usluga se ne može uistinu neovisno razvijati.

Dizajnirajte ugovore za promjene

API usluge trebao bi opisivati poslovne radnje, a ne otkrivati raspored svoje pohrane. Dajte prednost krajnjoj točki ili naredbi poput POST /payment-authorizations umjesto API-ja koji od pozivatelja traži da upravljaju redovima plaćanja. Vratite stabilne identifikatore i eksplicitna stanja. Dodajte polja kompatibilno, tolerirajte nepoznata polja gdje je to primjereno i uvodite verzije samo kada kompatibilna evolucija više nije moguća.

Mrežni pozivi mogu zakazati nakon što udaljena usluga dovrši rad, ali prije nego što pozivatelj primi odgovor. Za operacije koje se ne smiju dogoditi dvaput podržite idempotentnost. Klijent može priložiti jedinstveni ključ zahtjevu za plaćanje; usluga bilježi rezultat povezan s tim ključem i vraća ga pri ponovnom pokušaju umjesto da ponovno naplati.

Prihvatite eventualnu dosljednost tamo gdje joj je mjesto

Jedna transakcija baze podataka praktična je jer sve povezane promjene ili uspiju zajedno ili zajedno ne uspiju. Između usluga ta praktičnost nestaje. Pokušaj njezina ponovnog stvaranja distribuiranim transakcijama obično povećava krhkost i smanjuje dostupnost.

Umjesto toga, utvrdite invarijante koje doista zahtijevaju trenutačnu dosljednost. Rezerviranje zaliha prije potvrđivanja narudžbe može biti jedna od njih. Ažuriranje brojača na nadzornoj ploči obično nije. Za potonje objavite događaj i dopustite usluzi izvještavanja da asinkrono ažurira vlastiti model za čitanje.

Pouzdano objavljivanje događaja zahtijeva više od pisanja u red čekanja nakon potvrde transakcije baze podataka. Ako proces zakaže između ta dva koraka, promjena u bazi podataka postoji, ali događaj je izgubljen. Obrazac izlaznog spremnika rješava to: poslovnu promjenu i zapis događaja upišite u istoj lokalnoj transakciji, a zatim neka radnik isporučuje zapise na čekanju. Potrošači i dalje moraju biti idempotentni jer se isporuka može dogoditi više puta.

Izgradite operativnu disciplinu usporedno s uslugama

Svaka nova usluga dodaje odgovornosti za implementaciju, konfiguraciju, zapisivanje, provjere stanja, metrike i upozoravanja. Docker može lokalni razvoj učiniti ponovljivim, ali spremnik nije operativni model. Timovima je potreban jasan način da odgovore: koji je zahtjev zakazao, koja ga je ovisnost uzrokovala i je li ponovni pokušaj siguran?

Koristite strukturirane zapise s identifikatorima zahtjeva ili korelacije. Mjerite latenciju, stope pogrešaka, dubinu reda čekanja i kvarove ovisnosti. Postavite vremenska ograničenja za odlazne pozive; izostanak vremenskog ograničenja može iscrpiti PHP radnike dok čekaju nezdravu ovisnost. Ponovno pokušavajte samo prolazne kvarove, koristite ograničeno odgađanje i izbjegavajte ponavljanje ne-idempotentnih radnji osim ako usluga primateljica izričito podržava sigurnu deduplikaciju.

Implementirajte postupno. Usmjerite mali, reverzibilni dio prometa kroz novu putanju, usporedite ishode i zadržite put povratka dok raste povjerenje. Zastavice funkcionalnosti i testovi ugovora ovdje su vrijedni: prve kontroliraju izloženost, dok drugi provjeravaju slažu li se potrošači i pružatelji i dalje oko ponašanja.

Neka arhitektura bude zaslužena

Najuspješnije putovanje prema mikrouslugama obično nije glamurozno. Počinje modularnim monolitom, izdvaja jednu granicu koja je zaslužila neovisnost i uči iz proizašlog operativnog troška. Neke će domene ostati zajedno jer njihova pravila i transakcije pripadaju zajedno. To je dobro inženjerstvo, a ne nepotpuna transformacija.

Arhitektura bi trebala učiniti sljedeću važnu promjenu lakšom od prethodne. Ako to postiže manji, bolje strukturirani monolit, zadržite ga. Ako neovisno posjedovana usluga doista smanjuje koordinaciju, rizik implementacije ili pritisak skaliranja, pažljivo je izdvojite. Odredište nije dijagram ispunjen okvirima; to je sustav koji ostaje razumljiv, pouzdan i spreman za razvoj.

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.