10. Mikroteenuste andmestruktuurid: miks juba varakult planeerida?

 

Mikroteenuste andmestruktuurid: miks juba varakult planeerida?

Mikroteenuste arhitektuur lubab arendajatel jagada suur rakendus iseseisvateks teenusteks, mis võivad liikuda oma tempos. Tihti keskendutakse funktsionaalsuse tükeldamisele, kuid unustatakse andmed – kuidas andmed struktureerida ja millised on teenuste vahelised andmesuhted. Juba arenduse varajases staadiumis andmemudeli läbi mõtlemine on kriitilise tähtsusega, sest hiljem vigade parandamine on keerukas. Vaatleme, miks kiputakse selles faasis vigu tegema ning millised probleemid võivad ilmneda, kui andmesuhete disain unarusse jätta.

Varajane andmemudeli läbimõtlemine on kriitiline

Mikroteenuste maailmas eeldatakse, et iga teenus omab ja haldab oma andmeid ega jaga oma andmebaasi teistega​. See “data ownership” põhimõte tähendab, et teenuse andmebaasi tohib kasutada ainult läbi selle teenuse API või sündmuste (sünkroonne päring või asünkroonne sõnum)​. Monoliitse arhitektuuri puhul oli tavaline, et rakendusel on üks suur ühine andmebaas, mis tegi erinevate funktsioonide vahel joins’e lihtsaks. Mikroteenuste puhul see luksus puudub – iga teenuse andmeskeem on privaatne. Seetõttu tuleb juba alguses hoolikalt läbi mõelda, kuidas domeen mudelitesse jaguneb (Domain-Driven Design’i mõistega “bounded context” ehk piiritletud kontekst) ja kuidas teenuste andmed omavahel seostuvad. Kui neid piire ja andmesuhteid varakult ei planeeri, võib tulemuseks olla kaos: teenuste andmed on killustatud, kattuvad või vajavad pidevat sünkroniseerimist.

Levinud vead andmete struktureerimisel mikroteenustes

Mikroteenuste arhitektuuri juurutamisel nähakse sageli järgmisi probleeme:

  • Andmete killustatus ja integratsiooni raskused: Andmed jagunevad teenuste vahel nii, et tervikliku pildi saamiseks peab koguma infot mitmest allikast. Kuna ühist andmebaasi pole, muutub keeruliseks teha päringuid, mis liidavad mitme teenuse andmeid. Alternatiivina hakatakse rakendama andmete replikatsiooni – üks teenus hoiab koopia teise teenuse andmetest, et vältida liiga sageid võrgupäringuid. See toob aga kaasa eventual consistency probleemid: replikeeritud andmed on paratamatult teatud viitega ja võivad ajutiselt olla vananenud​. Arendajad peavad leppima, et täielik konsistents saabub viivitusega ning tegelema võimalike konfliktidega (nt kompenseerivate tehingutega)​.

  • Ristviited ja ühise andmebaasi kiusatus: Mikroteenustega algajad teevad tihti vea, püüdes mitmel teenusel lasta ühte andmebaasi jagada või otse teise teenuse andmebaasi pärida. See lähenemine rikkub teenuste iseseisvuse – kui üks teenus muudab skeemi või andmeid, mõjub see otse teisele ja tekitab ootamatuid kõrvalefekte​ ThoughtWorks’i eksperdid rõhutavad: ära jaga oma andmebaasi – iga teenus peab olema autonoomne nii loogika kui andmete osas​. Kui teenus vajab võõra teenuse andmeid, peaks ta küsima neid läbi API, mitte minema otse teise teenuse andmetele ligi​. Vastasel juhul tekib oht nn “distributed monolith” – hajutatud monoliit, kus teenused on nii tihedalt seotud, et ühe muutmine nõuab kõigi koordineeritud muutmist.

  • Andmete dubleerimine ja ebakõlad: Mikroteenuste arhitektuuris on tavaline, et osa andmeid dubleeritakse erinevates teenustes. Näiteks e-poe süsteemis võib ostude teenus hoida koopia kliendi andmetest, mis on pärit kliendiprofiili teenusest, et vähendada ristpäringuid. See läheb vastuollu klassikalise DRY (Don't Repeat Yourself) põhimõttega ning võib tekitada arendajates ebamugavust​. Samas on teatud määral andmete dubleerimine vältimatu, et teenused saaksid iseseisvalt töötada. Probleem tekib siis, kui dubleeritud andmeid ei hoita sünkroonis – ilma korraliku sündmuspõhise replikatsioonita tekivad teenuste vahel ebakõlalised tõeväärtused. AWS juhised märgivad, et detsentraliseeritud andmete puhul tuleb arvestada andmete sünkroniseerimise, tervikluse ja duplitseerimise väljakutsetega​.

  • Koordineerimise puudumine domeenide lõikes: Kui varases staadiumis ei lepita kokku selgetes domeeni piirides (bounded contexts) ja andmevastutustes, võivad erinevad meeskonnad luua ülekattega või vastuolulisi andmemudeleid. Näiteks võivad kaks teenust defineerida sama mõiste (nt "kasutaja") eri viisidel. Ilma koostööta tekib olukord, kus iga teenus “leiutab jalgratast” ning süsteemi globaalses pildis puudub ühtsus. Muudatuste tegemine nõuab siis hiljem suurt pingutust kõikide teenuste koordineerimisel. Microsofti pilootprojekt rõhutab, et iga mikroteenuse andmed ja mudel peavad olema kapseldatud – kui mitu teenust jagaksid sama andmestruktuuri, tuleks igasugune skeemimuudatus kooskõlastada kõigi osapooltega, mis pärsib mikroteenuste iseseisvat elutsüklit​. Halvasti piiritletud kontekstid võivadki väljenduda selles, et iga väiksemgi andmemudeli muudatus nõuab mitme teenuse ühist väljalaset – mikroteenused kaotavad oma eelise kiiruse osas.

Halb andmemudel pärsib skaleeritavust, kiirust ja hooldatavust

Ebakorrektselt disainitud andmesuhted võivad nullida mikroteenuste arhitektuuri eelised. Skaleeritavus kannatab, kui teenused pole õigesti eraldatud: näiteks kui üks teenus peab iga päringu korral küsima andmeid teistelt teenustelt, tekib kitsaskoht. Selle vältimiseks kasutataksegi vahel andmete kopeerimist lokaalselt – aga nagu mainitud, kaasneb sellega keerukus hoida andmeid värskena​. Samuti, kui teenused jagavad ühist andmebaasi, kaob võimalus skaleerida neid sõltumatult, sest andmebaasist saab globaalne pudelikael​ Hea andmemudel seevastu jagab koormuse loogiliselt: iga teenus saab skaleerida oma andmekihi vastavalt vajadusele, ilma et peaks terve süsteemi üles puhuma.

Arenduse kiirus ja agility võivad langeda drastiliselt, kui andmemudel on vildakas. Kujutame ette, et andmetabel on vales teenuses – iga uus funktsionaalsus nõuab muutusi kahes või enamas mikroteenuses korraga. See tähendab erinevate tiimide kooskõlastamist ja ühiselt planeeritud väljalaskeid, mis aeglustab iteratsiooni. Nagu üks Google Cloudi juhis märgib, tuleks teenuste piirid vajadusel üle vaadata, kui avastate, et teenused peavad liiga tihti üksteise andmeid otsima – see viitab valedele piiridele​.Õige piiritlemisega vähendatakse "chatty" suhtlust teenuste vahel ning iga tiim saab teha oma muudatusi sõltumatumalt.

Hooldatavus kannatab, kui andmete struktuur on segane. Hajutatud monoliidi olukorras – kus teenused on omavahel andmetega sõlmes – on vea jälitamine ja parandamine keeruline. Vigade puhul pole selge, milline teenus on tõe allikas, ning andmete sünkimiseks võivad puududa läbipaistvad mehhanismid. See lisab ops- ja debug’imiskoormust. Halb andmemudel kipub kaasa tooma ka tehnilise võla: koodis on rohkelt erijuhte andmete kokkusobitamiseks, dokumentatsioon võib ajapikku reaalsusest erineda ning uutel arendajatel on raske süsteemist aru saada. Vastupidiselt, selgete andmeomanduste ja piiridega süsteemi on lihtsam testida, monitoorida ja laiendada. Nagu AWS on täheldanud, parandab detsentraliseeritud, hästi läbimõeldud andmestrateegia süsteemi tõrketaluvust ning lühendab uute funktsioonide turule toomise aega​ – mikroteenused saavad siis tõesti oma eesmärki täita.

Hea andmemudeli põhimõtted mikroteenuste arhitektuuris

Kuidas siis vältida neid karisid? Esiteks, rakenda Domain-Driven Design lähenemist: jaga süsteem äriliste domeenide piiridesse (bounded contexts) ja defineeri selgelt, milline mikroteenus omab millist andmeid. Igal teenusel olgu oma “single source of truth” vastutus konkreetses andmedomeenis. See tähendab, et näiteks “kasutaja profiili” andmed paiknevad ainult kasutajateenuse andmebaasis ning teised teenused kasutavad neid läbi API või sündmuste. Sel moel on igal andmeüksusel selge omanik (data ownership), mis vähendab segadust ja dubleerimist. ThoughtWorks’i spetsialistid soovitavadki hoolikalt demarkeerida andmevastutused teenuste vahel DDD põhimõtete abil​.

Teiseks, lepi varakult kokku, kuidas toimub andmete sünkroonimine teenuste vahel. Paljude mikroteenuste süsteemide puhul pole võimalik tugevalt konsistentset (ACID) tehingut üle teenuste saavutada, seega tuleb disainis aksepteerida eventual consistency printsiip. Planeeri sündmuspõhine arhitektuur või kasutage näiteks sündmuste saatmist (event sourcing) ja sagadesse koondamist juhul, kui üks äriprotsess puudutab mitut teenust. Nii saab iga teenus oma andmeid kohe uuendada ning teavitada teisi – aja jooksul andmed ühtlustuvad. Oluline on jälgida, et hilinenud andmete saabumine ei põhjustaks lubamatuid olukordi; vajadusel implementeeri mehhanismid vananenud andmete tuvastamiseks ja kompenseerivateks meetmeteks. Koordineerimine on siinkohal võtmesõna: arhitektuuritiim või domeeni-eksperdid peaksid juba algfaasis looma ühise arusaama, kuidas andmevood üle teenuste jooksevad.

Lõpuks, ära karda andmete duplitseerimist, kui see on hästi hallatud. Kui kaks teenust vajavad sarnast infot, on parema skaleeritavuse huvides mõnikord otstarbekam hoida vajalikud atribuudid mõlemas andmebaasis, kui teha igaks päringuks kõne teisele teenusele​. Sellisel juhul dokumenteeri selgelt, milline teenus on andmete allikas ning milline hoiab vaid projektsiooni. Hästi disainitud projektsetud andmed (nt materialized view teisest teenusest) võivad vähendada latentsust ja suurendada süsteemi vastupidavust, tingimusel et mõistad kaasnevat vastutust andmete konsistentsuse tagamisel​. Kui tiimid mõistavad, millal jagada ja millal kopeerida andmeid, suudavad nad vältida tarbetuid pudelikaelu ja hoida süsteemi modulaarse.

Kokkuvõte

Mikroteenuste arhitektuuri edu sõltub suuresti hästi läbimõeldud andmemudelist. Juba varakult andmestruktuuridele ja suhetele tähelepanu pöörates väldid hilisemaid lõkse nagu killustunud andmesilo'd, omavahel tihedalt läbi põimunud teenused või dubleeritud ja ebatäpsed andmed. Halb andmemudel võib muuta su mikroteenused tagasi monoliidiks – raskesti skaleeritavaks, aeglaselt arenevaks ja keerukalt hallatavaks süsteemiks. Seevastu korralikult piiritletud kontektstid, selged andmeomandused ning läbimõeldud andmesünkroonsus annavad võimaluse luua süsteemi, mis on skaleeritav, paindlik uutele nõudmistele ja kergesti hooldatav. Mikroteenused ei tähenda vaid koodi jagamist väikesteks tükkideks – see tähendab ka andmete ja vastutuse tükeldamist mõistlikul viisil. Õppetund on selge: panusta aega andmemudeli disaini alguses, et võita aega ja kvaliteeti kogu ülejäänud arendustsükli vältel.

Kasutatud allikad: 

  1. thoughtworks.com 

  2. developers.redhat.com 

  3. docs.aws.amazon.com 

  4. learn.microsoft.com 

  5. martinfowler.com 

  6. cloud.google.com 

Kommentaarid

Populaarsed postitused sellest blogist

1. Miks Google prillid ebaõnnestusid?

2. Mis on alles ja mis on unustusse vajunud?

11. Scrumi rakendamine tarkvaraprojektis