Prehod z monolita na mikrostoritve: zgodovina in praksa

V tem članku bom govoril o tem, kako se je projekt, na katerem delam, preoblikoval iz velikega monolita v niz mikrostoritev.

Projekt je svojo zgodovino začel precej dolgo nazaj, v začetku leta 2000. Prve različice so bile napisane v Visual Basicu 6. Sčasoma je postalo jasno, da bo razvoj v tem jeziku v prihodnosti težko podpirati, saj je IDE in sam jezik sta slabo razvita. Konec 2000-ih je bilo odločeno preiti na bolj obetaven C#. Nova različica je nastajala vzporedno z revizijo stare, postopoma je bilo vse več kode napisane v .NET. Backend v C# je bil sprva osredotočen na storitveno arhitekturo, toda med razvojem so bile uporabljene skupne knjižnice z logiko in storitve so bile zagnane v enem samem procesu. Rezultat je bila aplikacija, ki smo jo poimenovali »storitveni monolit«.

Ena redkih prednosti te kombinacije je bila zmožnost storitev, da se medsebojno kličejo prek zunanjega API-ja. Obstajali so jasni predpogoji za prehod na pravilnejšo servisno in v prihodnosti mikrostoritveno arhitekturo.

Z delom na razgradnji smo začeli okoli leta 2015. Nismo še dosegli idealnega stanja - še vedno obstajajo deli velikega projekta, ki jih težko imenujemo monoliti, vendar tudi niso videti kot mikrostoritve. Kljub temu je napredek pomemben.
O tem bom govoril v članku.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Vsebina

Arhitektura in problemi obstoječe rešitve


Sprva je bila arhitektura videti takole: uporabniški vmesnik je ločena aplikacija, monolitni del je napisan v Visual Basicu 6, aplikacija .NET je niz povezanih storitev, ki delajo z dokaj veliko bazo podatkov.

Slabosti prejšnje rešitve

Ena sama točka napake
Imeli smo eno samo točko napake: aplikacija .NET je delovala v enem samem procesu. Če je kateri od modulov odpovedal, je odpovedala celotna aplikacija in jo je bilo treba znova zagnati. Ker avtomatiziramo veliko število procesov za različne uporabnike, zaradi okvare enega izmed njih vsi nekaj časa niso mogli delovati. In v primeru programske napake tudi varnostno kopiranje ni pomagalo.

Čakalna vrsta izboljšav
Ta pomanjkljivost je bolj organizacijska. Naša aplikacija ima veliko strank in vse si jo želijo čim prej izboljšati. Prej tega ni bilo mogoče storiti vzporedno in vse stranke so stale v vrsti. Ta proces je bil negativen za podjetja, ker so morala dokazati, da je njihova naloga dragocena. In razvojna ekipa je porabila čas za organizacijo te čakalne vrste. To je vzelo veliko časa in truda, izdelek pa se na koncu ni mogel spremeniti tako hitro, kot bi si želeli.

Neoptimalna uporaba virov
Pri gostovanju storitev v enem samem procesu smo konfiguracijo vedno popolnoma prepisali s strežnika na strežnik. Najbolj obremenjene storitve smo želeli postaviti ločeno, da ne bi zapravljali virov in pridobili bolj prilagodljiv nadzor nad našo shemo uvajanja.

Težko je izvajati sodobne tehnologije
Težava, ki je znana vsem razvijalcem: obstaja želja po uvedbi sodobnih tehnologij v projekt, vendar ni priložnosti. Z veliko monolitno rešitvijo se vsaka posodobitev trenutne knjižnice, da ne omenjamo prehoda na novo, spremeni v precej netrivialno nalogo. Dolgo traja, da se vodji ekipe dokaže, da bo to prineslo več bonusov kot izgubljenih živcev.

Težava pri izdajanju sprememb
To je bil najhujši problem - vsaka dva meseca smo izdajali izdaje.
Vsaka izdaja se je za banko spremenila v pravo katastrofo, kljub testiranju in trudu razvijalcev. Podjetje je v začetku tedna razumelo, da nekatere njegove funkcionalnosti ne bodo delovale. In razvijalci so razumeli, da jih čaka teden resnih incidentov.
Vsi so imeli željo spremeniti situacijo.

Pričakovanja od mikrostoritev


Izdaja komponent, ko so pripravljene. Dostava komponent, ko so pripravljene, z razgradnjo raztopine in ločevanjem različnih procesov.

Majhne produktne ekipe. To je pomembno, ker je bilo težko obvladovati veliko ekipo, ki je delala na starem monolitu. Takšna ekipa je bila prisiljena delati po strogem procesu, želela pa si je več ustvarjalnosti in samostojnosti. To so si lahko privoščile le majhne ekipe.

Izolacija storitev v ločenih procesih. V idealnem primeru sem ga želel izolirati v vsebnikih, vendar veliko število storitev, napisanih v .NET Framework, deluje samo v sistemu Windows. Storitve, ki temeljijo na .NET Core, se zdaj pojavljajo, vendar jih je še malo.

Prilagodljivost uvajanja. Storitve bi radi združevali tako, kot mi potrebujemo, in ne tako, kot sili kodeks.

Uporaba novih tehnologij. To je zanimivo za vsakega programerja.

Težave s prehodom


Seveda, če bi bilo enostavno razbiti monolit na mikrostoritve, o tem ne bi bilo treba govoriti na konferencah in pisati člankov. V tem procesu je veliko pasti, opisal bom glavne, ki so nas ovirale.

Prva težava značilno za večino monolitov: skladnost poslovne logike. Ko pišemo monolit, želimo znova uporabiti svoje razrede, da ne pišemo nepotrebne kode. In pri prehodu na mikrostoritve to postane težava: vsa koda je precej tesno povezana in storitve je težko ločiti.

Ob začetku dela je imelo skladišče več kot 500 projektov in več kot 700 tisoč vrstic kode. To je kar velika odločitev in drugi problem. Ni ga bilo mogoče preprosto vzeti in razdeliti na mikrostoritve.

Tretji problem — pomanjkanje potrebne infrastrukture. Pravzaprav smo ročno kopirali izvorno kodo na strežnike.

Kako preiti iz monolita v mikrostoritve


Zagotavljanje mikrostoritev

Prvič, sami smo takoj ugotovili, da je ločevanje mikrostoritev ponavljajoč se proces. Vedno smo morali vzporedno razvijati poslovne probleme. Kako bomo to tehnično izvedli, je že naš problem. Zato smo se pripravili na iterativni proces. Ne bo delovalo drugače, če imate veliko aplikacijo, ki na začetku še ni pripravljena za ponovno pisanje.

Katere metode uporabljamo za izolacijo mikrostoritev?

Prvi način — premakniti obstoječe module kot storitve. V zvezi s tem smo imeli srečo: že so bile registrirane storitve, ki so delovale s protokolom WCF. Razdelili so jih v ločene sklope. Prenesli smo jih ločeno in vsaki zgradbi dodali majhen zaganjalnik. Napisana je bila s čudovito knjižnico Topshelf, ki vam omogoča, da aplikacijo izvajate kot storitev in kot konzolo. To je priročno za odpravljanje napak, saj v rešitvi niso potrebni dodatni projekti.

Storitve so bile povezane po poslovni logiki, saj so uporabljale skupne sklope in delale s skupno bazo podatkov. Težko bi jih imenovali mikrostoritve v njihovi čisti obliki. Lahko pa te storitve izvajamo ločeno, v različnih procesih. Že samo to je omogočilo zmanjšanje njihovega vpliva drug na drugega, zmanjšanje problema vzporednega razvoja in ene same točke odpovedi.

Sestavljanje z gostiteljem je le ena vrstica kode v razredu programa. Delo s Topshelfom smo skrili v pomožni razred.

namespace RBA.Services.Accounts.Host
{
   internal class Program
   {
      private static void Main(string[] args)
      {
        HostRunner<Accounts>.Run("RBA.Services.Accounts.Host");

       }
    }
}

Drugi način dodeljevanja mikrostoritev je: ustvarite jih za reševanje novih problemov. Če hkrati monolit ne raste, je to že odlično, kar pomeni, da se premikamo v pravo smer. Za reševanje novih težav smo poskušali ustvariti ločene storitve. Če je obstajala takšna priložnost, smo ustvarili bolj "kanonične" storitve, ki popolnoma upravljajo svoj podatkovni model, ločeno bazo podatkov.

Tako kot mnogi smo začeli s storitvami avtentikacije in avtorizacije. Za to so kot nalašč. So neodvisni, praviloma imajo ločen podatkovni model. Sami ne sodelujejo z monolitom, le ta se obrne k njim, da rešijo nekatere težave. Z uporabo teh storitev lahko začnete prehod na novo arhitekturo, odpravite napake v infrastrukturi na njih, preizkusite nekaj pristopov, povezanih z omrežnimi knjižnicami itd. V naši organizaciji nimamo ekip, ki ne bi mogle ustvariti storitve preverjanja pristnosti.

Tretji način dodeljevanja mikrostoritevTisti, ki ga uporabljamo, je malo specifičen za nas. To je odstranitev poslovne logike iz plasti uporabniškega vmesnika. Naša glavna aplikacija uporabniškega vmesnika je namizna, tako kot zaledje je napisana v C#. Razvijalci so občasno delali napake in v uporabniški vmesnik prenesli dele logike, ki bi morali obstajati v ozadju in biti ponovno uporabljeni.

Če pogledate resničen primer kode dela uporabniškega vmesnika, lahko vidite, da večina te rešitve vsebuje resnično poslovno logiko, ki je uporabna v drugih procesih, ne samo za izdelavo obrazca uporabniškega vmesnika.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Prava logika uporabniškega vmesnika je le v zadnjih nekaj vrsticah. Prenesli smo ga na strežnik, da bi ga lahko ponovno uporabili, s čimer smo zmanjšali UI in dosegli pravilno arhitekturo.

Četrti in najpomembnejši način za izolacijo mikrostoritev, ki omogoča zmanjšanje monolitnosti, je odstranitev obstoječih storitev s predelavo. Ko vzamemo obstoječe module takšne, kot so, rezultat ni vedno všeč razvijalcem, poslovni proces pa je morda zastarel, odkar je bila funkcionalnost ustvarjena. S preoblikovanjem lahko podpremo nov poslovni proces, ker se poslovne zahteve nenehno spreminjajo. Izboljšamo lahko izvorno kodo, odstranimo znane napake in ustvarimo boljši podatkovni model. Nabirajo se številne koristi.

Ločevanje storitev od obdelave je neločljivo povezano s konceptom omejenega konteksta. To je koncept iz Domain Driven Design. Pomeni del modela domene, v katerem so vsi izrazi posameznega jezika enolično definirani. Oglejmo si kontekst zavarovanja in računov kot primer. Imamo monolitno aplikacijo in moramo delati z računom v zavarovanju. Pričakujemo, da bo razvijalec našel obstoječi razred računa v drugem sestavu, se nanj skliceval iz razreda zavarovanja in imeli bomo delujočo kodo. Upoštevano bo načelo DRY, z uporabo obstoječe kode bo naloga opravljena hitreje.

Posledično se izkaže, da sta konteksta računov in zavarovanja povezana. Ko se bodo pojavile nove zahteve, bo ta povezava ovirala razvoj in povečala kompleksnost že tako zapletene poslovne logike. Če želite rešiti to težavo, morate najti meje med konteksti v kodi in odstraniti njihove kršitve. Na primer, v kontekstu zavarovanja je povsem možno, da bosta zadostovala 20-mestna številka računa centralne banke in datum odprtja računa.

Da bi ločili te omejene kontekste drug od drugega in začeli postopek ločevanja mikrostoritev od monolitne rešitve, smo uporabili pristop, kot je ustvarjanje zunanjih API-jev znotraj aplikacije. Če smo vedeli, da mora neki modul postati mikrostoritev, nekako modificirana znotraj procesa, potem smo preko zunanjih klicev takoj poklicali logiko, ki pripada drugemu omejenemu kontekstu. Na primer prek REST ali WCF.

Trdno smo se odločili, da se ne bomo izogibali kodi, ki bi zahtevala porazdeljene transakcije. V našem primeru se je izkazalo, da je to pravilo povsem enostavno upoštevati. Nismo še naleteli na situacije, kjer bi bile res potrebne striktno porazdeljene transakcije – končna konsistentnost med moduli je povsem zadostna.

Poglejmo konkreten primer. Imamo koncept orkestratorja - cevovoda, ki obdeluje entiteto "aplikacije". Po vrsti ustvari stranko, račun in bančno kartico. Če sta stranka in račun uspešno ustvarjena, izdelava kartice pa ne uspe, aplikacija ne preide v status »uspešno« in ostane v statusu »kartica ni ustvarjena«. V prihodnosti ga bo pobrala in dokončala dejavnost v ozadju. Sistem je že nekaj časa v neusklajenosti, vendar smo s tem na splošno zadovoljni.

Če pride do situacije, ko bo potrebno dosledno shranjevati del podatkov, se bomo najverjetneje odločili za konsolidacijo storitve, da bi jo obdelali v enem procesu.

Oglejmo si primer dodeljevanja mikrostoritve. Kako ga lahko razmeroma varno pripeljete do proizvodnje? V tem primeru imamo ločen del sistema - servisni modul za obračun plač, katerega enega od delov kode bi radi naredili mikrostoritev.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Najprej ustvarimo mikrostoritev s prepisovanjem kode. Izboljšujemo nekatere vidike, s katerimi nismo bili zadovoljni. Uvajamo nove poslovne zahteve naročnika. Povezavi med uporabniškim vmesnikom in zaledjem dodamo API Gateway, ki bo zagotavljal posredovanje klicev.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Nato sprostimo to konfiguracijo v delovanje, vendar v pilotnem stanju. Večina naših uporabnikov še vedno dela s starimi poslovnimi procesi. Za nove uporabnike razvijamo novo različico monolitne aplikacije, ki tega procesa ne vsebuje več. V bistvu imamo kombinacijo monolita in mikrostoritve, ki deluje kot pilot.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Z uspešnim pilotom razumemo, da je nova konfiguracija dejansko izvedljiva, lahko odstranimo stari monolit iz enačbe in pustimo novo konfiguracijo namesto stare rešitve.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Skupaj uporabljamo skoraj vse obstoječe metode za razdelitev izvorne kode monolita. Vsi nam omogočajo, da zmanjšamo velikost delov aplikacije in jih prevedemo v nove knjižnice, s čimer naredimo boljšo izvorno kodo.

Delo z bazo podatkov


Bazo podatkov je mogoče razdeliti slabše od izvorne kode, saj ne vsebuje samo trenutne sheme, temveč tudi nakopičene zgodovinske podatke.

Naša zbirka podatkov je imela, tako kot mnoge druge, še eno pomembno pomanjkljivost - njeno ogromno velikost. Ta baza podatkov je bila zasnovana v skladu z zapleteno poslovno logiko monolita in odnosi, nakopičeni med tabelami različnih omejenih kontekstov.

V našem primeru se je poleg vseh težav (velika baza podatkov, veliko povezav, včasih nejasne meje med tabelami) pojavila težava, ki se pojavlja pri mnogih velikih projektih: uporaba predloge skupne baze podatkov. Podatki so bili vzeti iz tabel skozi pogled, prek podvajanja in poslani v druge sisteme, kjer je bilo to podvajanje potrebno. Posledično tabel nismo mogli premakniti v ločeno shemo, ker so bile aktivno uporabljene.

Enaka razdelitev na omejene kontekste v kodi nam pomaga pri ločevanju. Običajno nam daje precej dobro predstavo o tem, kako razčlenimo podatke na ravni baze podatkov. Razumemo, katere tabele pripadajo enemu omejenemu kontekstu in katere drugemu.

Uporabili smo dve globalni metodi particioniranja baze podatkov: particioniranje obstoječih tabel in particioniranje z obdelavo.

Ločevanje obstoječih tabel je dobra metoda za uporabo, če je struktura podatkov dobra, izpolnjuje poslovne zahteve in so z njo vsi zadovoljni. V tem primeru lahko obstoječe tabele ločimo v ločeno shemo.

Oddelek s predelavo potrebujemo, ko se je poslovni model močno spremenil in nas mize sploh ne zadovoljujejo več.

Razdelitev obstoječih tabel. Določiti moramo, kaj bomo ločili. Brez tega znanja nič ne bo delovalo in tukaj nam bo pomagalo ločevanje omejenih kontekstov v kodi. Praviloma, če razumete meje kontekstov v izvorni kodi, postane jasno, katere tabele je treba vključiti na seznam za oddelek.

Predstavljajmo si, da imamo rešitev, v kateri sta dva monolitna modula v interakciji z eno bazo podatkov. Zagotoviti moramo, da samo en modul komunicira z odsekom ločenih tabel, drugi pa začne z njim komunicirati prek API-ja. Za začetek je dovolj, da se samo snemanje izvaja prek API-ja. To je nujen pogoj, da lahko govorimo o neodvisnosti mikrostoritev. Bralne povezave lahko ostanejo, dokler ni večjih težav.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Naslednji korak je, da lahko razdelek kode, ki deluje z ločenimi tabelami, z ali brez obdelave, ločimo v ločeno mikrostoritev in jo izvajamo v ločenem procesu, vsebniku. To bo ločena storitev s povezavo do monolitne baze podatkov in tistih tabel, ki se nanjo ne nanašajo neposredno. Monolit še vedno sodeluje pri branju s snemljivim delom.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Kasneje bomo to povezavo odstranili, torej se bo v API preneslo tudi branje podatkov iz monolitne aplikacije iz ločenih tabel.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Nato bomo iz splošne baze podatkov izbrali tabele, s katerimi deluje samo nova mikrostoritev. Tabele lahko premaknemo v ločeno shemo ali celo v ločeno fizično bazo podatkov. Še vedno obstaja bralna povezava med mikrostoritvijo in monolitno bazo podatkov, vendar ni razloga za skrb, v tej konfiguraciji lahko živi precej dolgo.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Zadnji korak je popolna odstranitev vseh povezav. V tem primeru bomo morda morali preseliti podatke iz glavne baze podatkov. Včasih želimo ponovno uporabiti nekatere podatke ali imenike, podvojene iz zunanjih sistemov, v več zbirkah podatkov. To se nam občasno dogaja.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Oddelek za predelavo. Ta metoda je zelo podobna prvi, le v obratnem vrstnem redu. Takoj dodelimo novo bazo podatkov in novo mikrostoritev, ki komunicira z monolitom prek API-ja. Toda hkrati ostaja nabor tabel baze podatkov, ki jih želimo v prihodnosti izbrisati. Ne potrebujemo ga več, zamenjali smo ga v novem modelu.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Da bo ta shema delovala, bomo verjetno potrebovali prehodno obdobje.

Nato sta možna dva pristopa.

Prvič: podvajamo vse podatke v novi in ​​stari bazi. V tem primeru imamo redundanco podatkov in lahko pride do težav s sinhronizacijo. Lahko pa vzamemo dve različni stranki. Eden bo deloval z novo različico, drugi s staro.

Drugo: podatke delimo po nekaterih poslovnih kriterijih. V sistemu smo imeli na primer 5 izdelkov, ki so bili shranjeni v stari bazi podatkov. Šesto postavimo v novo poslovno nalogo v novo bazo. Vendar bomo potrebovali API Gateway, ki bo sinhroniziral te podatke in odjemalcu pokazal, kje in kaj naj dobi.

Oba pristopa delujeta, izberite glede na situacijo.

Ko se prepričamo, da vse deluje, lahko del monolita, ki deluje s starimi strukturami baze podatkov, onemogočimo.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Zadnji korak je odstranitev starih podatkovnih struktur.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Če povzamemo, lahko rečemo, da imamo težave z bazo podatkov: z njo je težko delati v primerjavi z izvorno kodo, težje jo je deliti, a to je mogoče in treba narediti. Našli smo nekaj načinov, ki nam to omogočajo precej varno, vendar je še vedno lažje delati napake s podatki kot z izvorno kodo.

Delo z izvorno kodo


Takole je izgledal diagram izvorne kode, ko smo začeli analizirati monolitni projekt.

Prehod z monolita na mikrostoritve: zgodovina in praksa

V grobem ga lahko razdelimo na tri plasti. To je plast zagnanih modulov, vtičnikov, storitev in posameznih dejavnosti. Pravzaprav so bile to vstopne točke znotraj monolitne rešitve. Vsi so bili tesno zaprti s skupno plastjo. Imel je poslovno logiko, da so si storitve delile in veliko povezav. Vsaka storitev in vtičnik sta uporabljala do 10 ali več skupnih sklopov, odvisno od njihove velikosti in vesti razvijalcev.

Imeli smo srečo, da smo imeli infrastrukturne knjižnice, ki bi jih lahko uporabljali ločeno.

Včasih je prišlo do situacije, ko nekateri običajni objekti dejansko niso pripadali tej plasti, ampak so bili infrastrukturne knjižnice. To so rešili s preimenovanjem.

Največja skrb so bili omejeni konteksti. Zgodilo se je, da so bili 3-4 konteksti pomešani v enem skupnem sestavu in so se med seboj uporabljali znotraj istih poslovnih funkcij. Treba je bilo razumeti, kje je to mogoče razdeliti in po kakšnih mejah ter kaj storiti naprej s preslikavo te delitve v sklope izvorne kode.

Oblikovali smo več pravil za postopek razdelitve kode.

1.: Nismo več želeli deliti poslovne logike med storitvami, dejavnostmi in vtičniki. Poslovno logiko smo želeli narediti neodvisno znotraj mikrostoritev. Po drugi strani pa so mikrostoritve idealno pojmovane kot storitve, ki obstajajo popolnoma neodvisno. Menim, da je ta pristop nekoliko potraten in težko dosegljiv, saj bodo na primer storitve v C# v vsakem primeru povezane s standardno knjižnico. Naš sistem je napisan v C#, drugih tehnologij še nismo uporabljali. Zato smo se odločili, da si lahko privoščimo uporabo običajnih tehničnih sklopov. Glavna stvar je, da ne vsebujejo nobenih drobcev poslovne logike. Če imate nad ORM, ki ga uporabljate, priročno ovojnico, je njeno kopiranje iz storitve v storitev zelo drago.

Naša ekipa je oboževalec oblikovanja, ki temelji na domenah, zato nam je čebulna arhitektura odlično ustrezala. Osnova naših storitev ni sloj dostopa do podatkov, temveč sklop z domensko logiko, ki vsebuje samo poslovno logiko in nima nobene povezave z infrastrukturo. Hkrati lahko samostojno spreminjamo sestav domene za reševanje problemov, povezanih z ogrodji.

Na tej stopnji smo naleteli na prvo resno težavo. Storitev se je morala nanašati na en domenski sklop, želeli smo narediti logiko neodvisno in DRY princip nas je tukaj močno oviral. Razvijalci so želeli ponovno uporabiti razrede iz sosednjih sklopov, da bi se izognili podvajanju, in posledično so se domene spet začele povezovati. Analizirali smo rezultate in se odločili, da je morda težava tudi na področju naprave za shranjevanje izvorne kode. Imeli smo velik repozitorij, ki je vseboval vso izvorno kodo. Rešitev za celoten projekt je bilo zelo težko sestaviti na lokalnem računalniku. Zato so bile za dele projekta ustvarjene ločene majhne rešitve in nihče ni prepovedal dodajanja skupnega ali domenskega sklopa in njihove ponovne uporabe. Edino orodje, ki nam tega ni omogočilo, je bil pregled kode. Včasih pa je tudi spodletelo.

Nato smo začeli prehajati na model z ločenimi repozitoriji. Poslovna logika se ne pretaka več od storitve do storitve, domene so se resnično osamosvojile. Omejeni konteksti so podprti bolj jasno. Kako ponovno uporabimo infrastrukturne knjižnice? Ločili smo jih v ločeno skladišče, nato pa v pakete Nuget, ki smo jih dali v Artifactory. Z vsako spremembo se sestavljanje in objava izvedeta samodejno.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Naše storitve so se začele sklicevati na notranje infrastrukturne pakete na enak način kot zunanje. Zunanje knjižnice prenašamo iz Nugeta. Za delo z Artifactory, kamor smo postavili te pakete, smo uporabili dva upravitelja paketov. V majhnih repozitorijih smo uporabili tudi Nuget. V repozitorijih z več storitvami smo uporabili Paket, ki zagotavlja večjo skladnost različic med moduli.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Tako z delom na izvorni kodi, rahlo spremembo arhitekture in ločevanjem repozitorijev naredimo naše storitve bolj neodvisne.

Težave z infrastrukturo


Večina slabosti prehoda na mikrostoritve je povezanih z infrastrukturo. Potrebovali boste avtomatizirano uvajanje, potrebovali boste nove knjižnice za zagon infrastrukture.

Ročna namestitev v okoljih

Sprva smo rešitev za okolja namestili ročno. Za avtomatizacijo tega procesa smo ustvarili cevovod CI/CD. Za proces kontinuirane dostave smo se odločili, ker kontinuirana uvedba za nas z vidika poslovnih procesov še ni sprejemljiva. Zato se pošiljanje v operacijo izvede s pomočjo gumba, za testiranje pa samodejno.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Uporabljamo Atlassian, Bitbucket za shranjevanje izvorne kode in Bamboo za gradnjo. Skripte za gradnjo radi pišemo v Cakeu, ker je enak C#. Pripravljeni paketi pridejo v Artifactory, Ansible pa samodejno pride do testnih strežnikov, po katerih jih je mogoče takoj preizkusiti.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Ločeno beleženje


Nekoč je bila ena od idej monolita zagotoviti skupno sečnjo. Prav tako smo morali razumeti, kaj storiti s posameznimi dnevniki, ki so na diskih. Naši dnevniki so zapisani v besedilne datoteke. Odločili smo se za uporabo standardnega sklada ELK. ELK nismo pisali neposredno prek ponudnikov, ampak smo se odločili, da bomo besedilne dnevnike spremenili in vanje zapisali ID sledenja kot identifikator ter dodali ime storitve, tako da bo te dnevnike mogoče razčleniti pozneje.

Prehod z monolita na mikrostoritve: zgodovina in praksa

Z uporabo Filebeata dobimo priložnost, da zberemo svoje dnevnike s strežnikov, jih nato preoblikujemo, uporabimo Kibano za ustvarjanje poizvedb v uporabniškem vmesniku in vidimo, kako je potekal klic med storitvami. Trace ID pri tem zelo pomaga.

Storitve, povezane s testiranjem in odpravljanjem napak


Na začetku nismo popolnoma razumeli, kako odpraviti napake v storitvah, ki se razvijajo. Z monolitom je bilo vse preprosto, zagnali smo ga na lokalnem stroju. Sprva so poskušali storiti enako z mikrostoritvami, včasih pa morate za popoln zagon ene mikrostoritve zagnati več drugih, kar je neprijetno. Ugotovili smo, da se moramo premakniti na model, kjer na lokalnem računalniku pustimo samo storitev ali storitve, ki jih želimo odpraviti. Preostale storitve se uporabljajo s strežnikov, ki ustrezajo konfiguraciji s prod. Po odpravljanju napak, med testiranjem, se za vsako nalogo testnemu strežniku izdajo samo spremenjene storitve. Tako se rešitev testira v obliki, v kakršni se bo v prihodnosti pojavila v proizvodnji.

Obstajajo strežniki, ki izvajajo samo proizvodne različice storitev. Ti strežniki so potrebni v primeru incidentov, za preverjanje dostave pred uvedbo in za interno usposabljanje.

Dodali smo avtomatiziran postopek testiranja s priljubljeno knjižnico Specflow. Preizkusi se izvajajo samodejno z uporabo NUnit takoj po uvedbi iz Ansible. Če je pokritost nalog popolnoma avtomatska, ni potrebe po ročnem testiranju. Čeprav je včasih še vedno potrebno dodatno ročno testiranje. V Jiri uporabljamo oznake, da določimo, katere teste izvesti za določeno težavo.

Poleg tega se je povečala potreba po testiranju obremenitve, ki se je prej izvajalo le v redkih primerih. JMeter uporabljamo za izvajanje testov, InfluxDB za njihovo shranjevanje in Grafano za gradnjo grafov procesov.

Kaj smo dosegli?


Prvič, znebili smo se pojma "sprostitev". Minili so dvomesečne pošastne izdaje, ko je bil ta kolos nameščen v proizvodnem okolju in je začasno motil poslovne procese. Zdaj storitve uvajamo v povprečju vsakih 1,5 dni in jih združujemo v skupine, ker začnejo delovati po odobritvi.

V našem sistemu ni usodnih napak. Če izdamo mikrostoritev z napako, bo z njo povezana funkcionalnost pokvarjena, vse druge funkcionalnosti pa ne bodo prizadete. To močno izboljša uporabniško izkušnjo.

Lahko nadzorujemo vzorec uvajanja. Po potrebi lahko izberete skupine storitev ločeno od preostale rešitve.

Poleg tega smo znatno zmanjšali težavo z veliko čakalno vrsto izboljšav. Zdaj imamo ločene produktne skupine, ki delajo z nekaterimi storitvami neodvisno. Postopek Scrum se tukaj že dobro prilega. Določena ekipa ima lahko ločenega lastnika izdelka, ki ji dodeljuje naloge.

Povzetek

  • Mikrostoritve so zelo primerne za razgradnjo kompleksnih sistemov. V procesu začnemo razumeti, kaj je v našem sistemu, kateri omejeni konteksti obstajajo, kje so njihove meje. To vam omogoča, da izboljšave pravilno porazdelite med module in preprečite zmedo kode.
  • Mikrostoritve zagotavljajo organizacijske koristi. Pogosto se o njih govori le kot o arhitekturi, a vsaka arhitektura je potrebna za reševanje poslovnih potreb in ne sama zase. Zato lahko rečemo, da so mikrostoritve zelo primerne za reševanje problemov v majhnih ekipah, saj je Scrum trenutno zelo priljubljen.
  • Ločevanje je ponavljajoč se proces. Ne morete vzeti aplikacije in jo preprosto razdeliti na mikrostoritve. Nastali izdelek verjetno ne bo deloval. Pri nameščanju mikrostoritev je koristno obstoječo zapuščino prepisati, torej spremeniti v kodo, ki nam je všeč in bolj ustreza poslovnim potrebam glede funkcionalnosti in hitrosti.

    Majhno opozorilo: Stroški prehoda na mikrostoritve so precejšnji. Samo reševanje problema infrastrukture je trajalo dolgo. Torej, če imate majhno aplikacijo, ki ne zahteva posebnega skaliranja, razen če imate veliko število strank, ki tekmujejo za pozornost in čas vaše ekipe, mikrostoritve morda danes niso tisto, kar potrebujete. Je kar drago. Če začnete proces z mikrostoritvami, potem bodo stroški na začetku višji, kot če isti projekt začnete z razvojem monolita.

    PS Bolj čustvena zgodba (in kot za vas osebno) - glede na povezava.
    Tukaj je celotna različica poročila.

Vir: www.habr.com

Dodaj komentar