Overgangen fra monolitt til mikrotjenester: historie og praksis

I denne artikkelen skal jeg snakke om hvordan prosjektet jeg jobber med forvandlet seg fra en stor monolitt til et sett med mikrotjenester.

Prosjektet begynte sin historie for ganske lenge siden, i begynnelsen av 2000. De første versjonene ble skrevet i Visual Basic 6. Over tid ble det klart at utvikling på dette språket ville være vanskelig å støtte i fremtiden, siden IDE og språket i seg selv er dårlig utviklet. På slutten av 2000-tallet ble det besluttet å bytte til det mer lovende C#. Den nye versjonen ble skrevet parallelt med revisjonen av den gamle, etter hvert ble det skrevet mer og mer kode i .NET. Backend i C# var i utgangspunktet fokusert på en tjenestearkitektur, men under utviklingen ble det brukt vanlige biblioteker med logikk, og tjenester ble lansert i en enkelt prosess. Resultatet var en applikasjon som vi kalte en "tjenestemonolit."

En av de få fordelene med denne kombinasjonen var tjenestenes evne til å ringe hverandre gjennom en ekstern API. Det var klare forutsetninger for overgangen til en mer korrekt tjeneste, og i fremtiden, mikrotjenestearkitektur.

Vi startet arbeidet med dekomponering rundt 2015. Vi har ennå ikke nådd en ideell tilstand – det er fortsatt deler av et stort prosjekt som knapt kan kalles monolitter, men de ser heller ikke ut som mikrotjenester. Likevel er fremgangen betydelig.
Jeg vil snakke om det i artikkelen.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Innhold

Arkitektur og problemer med eksisterende løsning


Opprinnelig så arkitekturen slik ut: UI er en egen applikasjon, den monolittiske delen er skrevet i Visual Basic 6, .NET-applikasjonen er et sett med relaterte tjenester som jobber med en ganske stor database.

Ulemper med forrige løsning

Enkeltpunkt for feil
Vi hadde et enkelt feilpunkt: .NET-applikasjonen kjørte i en enkelt prosess. Hvis en modul mislyktes, mislyktes hele applikasjonen og måtte startes på nytt. Siden vi automatiserer et stort antall prosesser for forskjellige brukere, på grunn av feil i en av dem, kunne ikke alle jobbe på en stund. Og i tilfelle en programvarefeil hjalp ikke engang sikkerhetskopiering.

Kø av forbedringer
Denne ulempen er ganske organisatorisk. Vår applikasjon har mange kunder, og de ønsker alle å forbedre den så snart som mulig. Tidligere var det umulig å gjøre dette parallelt, og alle kundene sto i kø. Denne prosessen var negativ for virksomheter fordi de måtte bevise at oppgaven deres var verdifull. Og utviklingsteamet brukte tid på å organisere denne køen. Dette tok mye tid og krefter, og produktet kunne til slutt ikke endres så raskt som de skulle ha ønsket.

Suboptimal bruk av ressurser
Når vi hoster tjenester i en enkelt prosess, kopierte vi alltid konfigurasjonen fullstendig fra server til server. Vi ønsket å plassere de mest belastede tjenestene separat for ikke å sløse med ressurser og få mer fleksibel kontroll over distribusjonsordningen vår.

Vanskelig å implementere moderne teknologier
Et problem kjent for alle utviklere: det er et ønske om å introdusere moderne teknologier i prosjektet, men det er ingen mulighet. Med en stor monolittisk løsning blir enhver oppdatering av det nåværende biblioteket, for ikke å nevne overgangen til et nytt, til en ganske ikke-triviell oppgave. Det tar lang tid å bevise for laglederen at dette vil gi flere bonuser enn bortkastede nerver.

Vanskeligheter med å utstede endringer
Dette var det mest alvorlige problemet - vi ga ut utgivelser annenhver måned.
Hver utgivelse ble til en virkelig katastrofe for banken, til tross for testing og innsats fra utviklerne. Bedriften forsto at i begynnelsen av uken ville noe av funksjonaliteten ikke fungere. Og utviklerne forsto at en uke med alvorlige hendelser ventet dem.
Alle hadde et ønske om å endre situasjonen.

Forventninger fra mikrotjenester


Utstedelse av komponenter når de er klare. Levering av komponenter når de er klare ved å dekomponere løsningen og separere ulike prosesser.

Små produktteam. Dette er viktig fordi et stort team som jobbet med den gamle monolitten var vanskelig å administrere. Et slikt team ble tvunget til å jobbe i henhold til en streng prosess, men de ønsket mer kreativitet og selvstendighet. Bare små lag hadde råd til dette.

Isolering av tjenester i separate prosesser. Ideelt sett ønsket jeg å isolere det i containere, men et stort antall tjenester skrevet i .NET Framework kjører kun på Windows. Tjenester basert på .NET Core dukker nå opp, men det er få av dem ennå.

Fleksibilitet for distribusjon. Vi vil gjerne kombinere tjenester slik vi trenger det, og ikke slik koden tvinger det.

Bruk av ny teknologi. Dette er interessant for enhver programmerer.

Overgangsproblemer


Selvfølgelig, hvis det var enkelt å bryte en monolitt i mikrotjenester, ville det ikke vært behov for å snakke om det på konferanser og skrive artikler. Det er mange fallgruver i denne prosessen; jeg vil beskrive de viktigste som hindret oss.

Det første problemet typisk for de fleste monolitter: sammenheng i forretningslogikk. Når vi skriver en monolitt, ønsker vi å gjenbruke klassene våre for ikke å skrive unødvendig kode. Og når du flytter til mikrotjenester, blir dette et problem: all koden er ganske tett koblet, og det er vanskelig å skille tjenestene.

På tidspunktet for oppstart av arbeidet hadde depotet mer enn 500 prosjekter og mer enn 700 tusen linjer med kode. Dette er en ganske stor avgjørelse og andre problem. Det var ikke mulig å bare ta det og dele det inn i mikrotjenester.

Tredje problem — mangel på nødvendig infrastruktur. Faktisk kopierte vi kildekoden manuelt til serverne.

Hvordan gå fra monolitt til mikrotjenester


Provisioning Microservices

For det første bestemte vi oss umiddelbart for at separasjonen av mikrotjenester er en iterativ prosess. Vi ble alltid pålagt å utvikle forretningsproblemer parallelt. Hvordan vi skal implementere dette teknisk er allerede vårt problem. Derfor forberedte vi oss på en iterativ prosess. Det vil ikke fungere på noen annen måte hvis du har en stor applikasjon og den i utgangspunktet ikke er klar til å skrives om.

Hvilke metoder bruker vi for å isolere mikrotjenester?

Den første måten — flytte eksisterende moduler som tjenester. I denne forbindelse var vi heldige: det var allerede registrerte tjenester som fungerte ved hjelp av WCF-protokollen. De ble delt inn i separate forsamlinger. Vi porterte dem separat, og la til en liten launcher til hver bygg. Den ble skrevet ved hjelp av det fantastiske Topshelf-biblioteket, som lar deg kjøre applikasjonen både som en tjeneste og som en konsoll. Dette er praktisk for feilsøking siden det ikke kreves flere prosjekter i løsningen.

Tjenestene ble koblet sammen i henhold til forretningslogikk, siden de brukte felles sammenstillinger og jobbet med en felles database. De kan knapt kalles mikrotjenester i sin rene form. Vi kan imidlertid tilby disse tjenestene separat, i forskjellige prosesser. Dette alene gjorde det mulig å redusere deres innflytelse på hverandre, og reduserte problemet med parallell utvikling og et enkelt feilpunkt.

Montering med verten er bare én kodelinje i programklassen. Vi gjemte arbeid med Topshelf i en hjelpeklasse.

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

       }
    }
}

Den andre måten å tildele mikrotjenester på er: skape dem for å løse nye problemer. Hvis monolitten samtidig ikke vokser, er dette allerede utmerket, noe som betyr at vi beveger oss i riktig retning. For å løse nye problemer prøvde vi å lage separate tjenester. Hvis det var en slik mulighet, så laget vi mer "kanoniske" tjenester som fullstendig styrer sin egen datamodell, en egen database.

Vi, som mange, startet med autentiserings- og autorisasjonstjenester. De er perfekte for dette. De er uavhengige, som regel har de en egen datamodell. De selv samhandler ikke med monolitten, bare den henvender seg til dem for å løse noen problemer. Ved å bruke disse tjenestene kan du begynne overgangen til en ny arkitektur, feilsøke infrastrukturen på dem, prøve noen tilnærminger relatert til nettverksbiblioteker, etc. Vi har ingen team i organisasjonen vår som ikke kunne opprette en autentiseringstjeneste.

Den tredje måten å tildele mikrotjenester påDen vi bruker er litt spesifikk for oss. Dette er fjerning av forretningslogikk fra UI-laget. Vår viktigste brukergrensesnittapplikasjon er skrivebordet; den, som backend, er skrevet i C#. Utviklerne gjorde med jevne mellomrom feil og overførte deler av logikken til brukergrensesnittet som skulle ha eksistert i backend og blitt gjenbrukt.

Hvis du ser på et ekte eksempel fra koden til UI-delen, kan du se at det meste av denne løsningen inneholder ekte forretningslogikk som er nyttig i andre prosesser, ikke bare for å bygge UI-skjemaet.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Den virkelige UI-logikken er der bare i de siste par linjene. Vi overførte den til serveren slik at den kunne gjenbrukes, og derved reduserte brukergrensesnittet og oppnådde riktig arkitektur.

Den fjerde og viktigste måten å isolere mikrotjenester på, som gjør det mulig å redusere monolitten, er fjerning av eksisterende tjenester med prosessering. Når vi tar ut eksisterende moduler som de er, er ikke resultatet alltid etter utviklernes smak, og forretningsprosessen kan ha blitt utdatert siden funksjonaliteten ble opprettet. Med refactoring kan vi støtte en ny forretningsprosess fordi forretningskravene stadig endres. Vi kan forbedre kildekoden, fjerne kjente defekter og lage en bedre datamodell. Det er mange fordeler som oppstår.

Å skille tjenester fra behandling er uløselig knyttet til begrepet avgrenset kontekst. Dette er et konsept fra Domain Driven Design. Det betyr en del av domenemodellen der alle vilkårene for et enkelt språk er unikt definert. La oss se på sammenhengen med forsikring og regninger som et eksempel. Vi har en monolittisk søknad, og vi må jobbe med kontoen i forsikring. Vi forventer at utvikleren finner en eksisterende kontoklasse i en annen sammenstilling, refererer til den fra forsikringsklassen, så har vi en fungerende kode. DRY-prinsippet vil bli respektert, oppgaven vil gjøres raskere ved å bruke eksisterende kode.

Som et resultat viser det seg at kontekstene regnskap og forsikring henger sammen. Etter hvert som nye krav dukker opp, vil denne koblingen forstyrre utviklingen, og øke kompleksiteten til allerede kompleks forretningslogikk. For å løse dette problemet må du finne grensene mellom kontekster i koden og fjerne bruddene deres. For eksempel, i forsikringssammenheng, er det godt mulig at et 20-sifret sentralbankkontonummer og datoen kontoen ble åpnet vil være tilstrekkelig.

For å skille disse avgrensede kontekstene fra hverandre og begynne prosessen med å skille mikrotjenester fra en monolitisk løsning, brukte vi en tilnærming som å lage eksterne APIer i applikasjonen. Hvis vi visste at en modul skulle bli en mikrotjeneste, på en eller annen måte modifisert i prosessen, så ringte vi umiddelbart til logikken som tilhører en annen begrenset kontekst gjennom eksterne samtaler. For eksempel via REST eller WCF.

Vi bestemte oss bestemt for at vi ikke ville unngå kode som ville kreve distribuerte transaksjoner. I vårt tilfelle viste det seg å være ganske enkelt å følge denne regelen. Vi har ennå ikke støtt på situasjoner der det virkelig er behov for strenge distribuerte transaksjoner - den endelige konsistensen mellom modulene er ganske tilstrekkelig.

La oss se på et spesifikt eksempel. Vi har konseptet med en orkestrator - en rørledning som behandler enheten til "applikasjonen". Han oppretter en klient, en konto og et bankkort etter tur. Hvis klienten og kontoen er opprettet, men kortopprettelsen mislykkes, flyttes ikke applikasjonen til statusen "vellykket" og forblir i statusen "kortet ikke opprettet". I fremtiden vil bakgrunnsaktivitet plukke den opp og fullføre den. Systemet har vært i en tilstand av inkonsekvens en stund, men vi er generelt fornøyd med dette.

Hvis det oppstår en situasjon hvor det er nødvendig å konsekvent lagre deler av dataene, vil vi mest sannsynlig gå for konsolidering av tjenesten for å behandle den i én prosess.

La oss se på et eksempel på tildeling av en mikrotjeneste. Hvordan kan du bringe den til produksjon relativt trygt? I dette eksemplet har vi en egen del av systemet - en lønnstjenestemodul, en av kodedelene som vi ønsker å lage mikroservice av.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Først av alt lager vi en mikrotjeneste ved å skrive om koden. Vi forbedrer noen aspekter som vi ikke var fornøyd med. Vi implementerer nye forretningskrav fra kunden. Vi legger til en API-gateway i forbindelsen mellom brukergrensesnittet og backend, som vil gi viderekobling.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Deretter slipper vi denne konfigurasjonen i drift, men i en pilottilstand. De fleste av våre brukere jobber fortsatt med gamle forretningsprosesser. For nye brukere utvikler vi en ny versjon av den monolittiske applikasjonen som ikke lenger inneholder denne prosessen. I hovedsak har vi en kombinasjon av en monolitt og en mikrotjeneste som jobber som pilot.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Med en vellykket pilot forstår vi at den nye konfigurasjonen faktisk er gjennomførbar, vi kan fjerne den gamle monolitten fra ligningen og la den nye konfigurasjonen stå i stedet for den gamle løsningen.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Totalt bruker vi nesten alle eksisterende metoder for å dele kildekoden til en monolitt. Alle lar oss redusere størrelsen på deler av applikasjonen og oversette dem til nye biblioteker, noe som gir bedre kildekode.

Arbeid med databasen


Databasen kan deles dårligere enn kildekoden, siden den inneholder ikke bare gjeldende skjema, men også akkumulerte historiske data.

Vår database, som mange andre, hadde en annen viktig ulempe - dens enorme størrelse. Denne databasen ble designet i henhold til den intrikate forretningslogikken til en monolitt, og relasjoner akkumulerte mellom tabellene i forskjellige avgrensede kontekster.

I vårt tilfelle, for å toppe alle problemene (stor database, mange forbindelser, noen ganger uklare grenser mellom tabeller), oppsto et problem som oppstår i mange store prosjekter: bruken av den delte databasemalen. Data ble hentet fra tabeller gjennom visning, gjennom replikering og sendt til andre systemer der denne replikeringen var nødvendig. Som et resultat kunne vi ikke flytte tabellene inn i et eget skjema fordi de ble aktivt brukt.

Den samme inndelingen i begrensede sammenhenger i koden hjelper oss i separasjonen. Det gir oss vanligvis en ganske god idé om hvordan vi bryter ned dataene på databasenivå. Vi forstår hvilke tabeller som tilhører en avgrenset kontekst og hvilke til en annen.

Vi brukte to globale metoder for databasepartisjonering: partisjonering av eksisterende tabeller og partisjonering med prosessering.

Å dele opp eksisterende tabeller er en god metode å bruke dersom datastrukturen er god, oppfyller forretningskrav, og alle er fornøyde med det. I dette tilfellet kan vi skille eksisterende tabeller inn i et eget skjema.

En avdeling med prosessering er nødvendig når forretningsmodellen har endret seg mye, og tabellene ikke lenger tilfredsstiller oss i det hele tatt.

Deling av eksisterende tabeller. Vi må bestemme hva vi skal skille. Uten denne kunnskapen vil ingenting fungere, og her vil separasjonen av avgrensede kontekster i koden hjelpe oss. Som regel, hvis du kan forstå grensene for kontekster i kildekoden, blir det klart hvilke tabeller som skal inkluderes i listen for avdelingen.

La oss forestille oss at vi har en løsning der to monolittmoduler samhandler med én database. Vi må sørge for at bare én modul samhandler med delen av separerte tabeller, og den andre begynner å samhandle med den via API. Til å begynne med er det nok at bare opptak utføres gjennom API. Dette er en nødvendig betingelse for at vi skal snakke om mikrotjenesters uavhengighet. Leseforbindelser kan forbli så lenge det ikke er store problemer.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Det neste trinnet er at vi kan skille kodedelen som fungerer med separerte tabeller, med eller uten prosessering, til en egen mikrotjeneste og kjøre den i en egen prosess, en container. Dette vil være en egen tjeneste med kobling til monolittdatabasen og de tabellene som ikke er direkte relatert til den. Monolitten samhandler fortsatt for lesing med den avtakbare delen.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Senere vil vi fjerne denne forbindelsen, det vil si at lesing av data fra en monolittisk applikasjon fra separerte tabeller også vil bli overført til API.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Deretter vil vi velge fra den generelle databasen tabellene som bare den nye mikrotjenesten fungerer med. Vi kan flytte tabellene til et eget skjema eller til og med til en egen fysisk database. Det er fortsatt en leseforbindelse mellom mikrotjenesten og monolittdatabasen, men det er ingenting å bekymre seg for, i denne konfigurasjonen kan den leve ganske lenge.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Det siste trinnet er å fjerne alle tilkoblinger fullstendig. I dette tilfellet må vi kanskje migrere data fra hoveddatabasen. Noen ganger ønsker vi å gjenbruke noen data eller kataloger replikert fra eksterne systemer i flere databaser. Dette skjer med oss ​​med jevne mellomrom.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Behandlingsavdeling. Denne metoden er veldig lik den første, bare i omvendt rekkefølge. Vi tildeler umiddelbart en ny database og en ny mikrotjeneste som samhandler med monolitten via en API. Men samtidig gjenstår det et sett med databasetabeller som vi ønsker å slette i fremtiden. Vi trenger den ikke lenger, vi erstattet den i den nye modellen.

Overgangen fra monolitt til mikrotjenester: historie og praksis

For at denne ordningen skal fungere, vil vi sannsynligvis trenge en overgangsperiode.

Det er da to mulige tilnærminger.

Første: vi dupliserer alle data i de nye og gamle databasene. I dette tilfellet har vi dataredundans og synkroniseringsproblemer kan oppstå. Men vi kan ta to forskjellige klienter. Den ene vil jobbe med den nye versjonen, den andre med den gamle.

Sekund: vi deler dataene i henhold til noen forretningskriterier. For eksempel hadde vi 5 produkter i systemet som var lagret i den gamle databasen. Vi plasserer den sjette innenfor den nye forretningsoppgaven i en ny database. Men vi trenger en API-gateway som vil synkronisere disse dataene og vise klienten hvor og hva den skal komme fra.

Begge tilnærmingene fungerer, velg avhengig av situasjonen.

Etter at vi er sikre på at alt fungerer, kan den delen av monolitten som fungerer med gamle databasestrukturer deaktiveres.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Det siste trinnet er å fjerne de gamle datastrukturene.

Overgangen fra monolitt til mikrotjenester: historie og praksis

For å oppsummere kan vi si at vi har problemer med databasen: det er vanskelig å jobbe med den sammenlignet med kildekoden, det er vanskeligere å dele, men det kan og bør gjøres. Vi har funnet noen måter som gjør at vi kan gjøre dette ganske trygt, men det er fortsatt lettere å gjøre feil med data enn med kildekode.

Jobber med kildekode


Slik så kildekodediagrammet ut da vi begynte å analysere det monolittiske prosjektet.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Den kan grovt sett deles inn i tre lag. Dette er et lag med lanserte moduler, plugins, tjenester og individuelle aktiviteter. Faktisk var dette inngangspunkter innenfor en monolittisk løsning. Alle ble tett forseglet med et felles lag. Det hadde forretningslogikk som tjenestene delte og mange forbindelser. Hver tjeneste og plugin brukte opptil 10 eller flere vanlige sammenstillinger, avhengig av størrelsen og samvittigheten til utviklerne.

Vi var heldige som hadde infrastrukturbiblioteker som kunne brukes separat.

Noen ganger oppsto det en situasjon der noen vanlige objekter faktisk ikke tilhørte dette laget, men var infrastrukturbiblioteker. Dette ble løst ved å gi nytt navn.

Den største bekymringen var avgrensede sammenhenger. Det hendte at 3-4 sammenhenger ble blandet i én Fellesforsamling og brukte hverandre innenfor de samme forretningsfunksjonene. Det var nødvendig å forstå hvor dette kunne deles og langs hvilke grenser, og hva man skulle gjøre videre med å kartlegge denne inndelingen i kildekodesammenstillinger.

Vi har formulert flere regler for kodedelingsprosessen.

Første: Vi ønsket ikke lenger å dele forretningslogikk mellom tjenester, aktiviteter og plugins. Vi ønsket å gjøre forretningslogikk uavhengig innen mikrotjenester. Mikrotjenester på den annen side er ideelt sett tenkt som tjenester som eksisterer helt uavhengig. Jeg mener at denne tilnærmingen er noe bortkastet, og den er vanskelig å få til, fordi for eksempel tjenester i C# uansett vil være koblet sammen av et standardbibliotek. Systemet vårt er skrevet i C#; vi har ennå ikke brukt andre teknologier. Derfor bestemte vi oss for at vi hadde råd til å bruke felles tekniske forsamlinger. Hovedsaken er at de ikke inneholder noen fragmenter av forretningslogikk. Hvis du har en praktisk innpakning over ORM-en du bruker, er det svært kostbart å kopiere den fra tjeneste til tjeneste.

Teamet vårt er en fan av domenedrevet design, så løkarkitektur passet perfekt for oss. Grunnlaget for tjenestene våre er ikke datatilgangslaget, men en sammenstilling med domenelogikk, som kun inneholder forretningslogikk og ikke har noen forbindelser med infrastrukturen. Samtidig kan vi selvstendig modifisere domenesammenstillingen for å løse problemer knyttet til rammeverk.

På dette stadiet møtte vi vårt første alvorlige problem. Tjenesten måtte referere til én domenesammenstilling, vi ønsket å gjøre logikken uavhengig, og DRY-prinsippet hemmet oss i stor grad her. Utviklerne ønsket å gjenbruke klasser fra nærliggende forsamlinger for å unngå duplisering, og som et resultat begynte domener å bli koblet sammen igjen. Vi analyserte resultatene og bestemte at problemet kanskje også ligger i området for kildekodelagringsenheten. Vi hadde et stort depot som inneholdt all kildekoden. Løsningen for hele prosjektet var svært vanskelig å montere på en lokal maskin. Derfor ble det laget egne små løsninger for deler av prosjektet, og ingen forbød å legge til noen felles- eller domenemontering til dem og gjenbruke dem. Det eneste verktøyet som ikke tillot oss å gjøre dette var kodegjennomgang. Men noen ganger mislyktes det også.

Så begynte vi å gå over til en modell med separate depoter. Forretningslogikk flyter ikke lenger fra tjeneste til tjeneste, domener har virkelig blitt uavhengige. Avgrensede kontekster støttes tydeligere. Hvordan gjenbruker vi infrastrukturbiblioteker? Vi delte dem i et eget depot, og la dem deretter inn i Nuget-pakker, som vi la inn i Artifactory. Ved enhver endring skjer montering og publisering automatisk.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Tjenestene våre begynte å referere til interne infrastrukturpakker på samme måte som eksterne. Vi laster ned eksterne biblioteker fra Nuget. For å jobbe med Artifactory, hvor vi plasserte disse pakkene, brukte vi to pakkebehandlere. I små depoter brukte vi også Nuget. I depoter med flere tjenester brukte vi Paket, som gir mer versjonskonsistens mellom moduler.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Ved å jobbe med kildekoden, endre litt på arkitekturen og separere depotene, gjør vi tjenestene våre mer uavhengige.

Infrastrukturproblemer


De fleste av ulempene med å flytte til mikrotjenester er infrastrukturrelatert. Du trenger automatisert distribusjon, du trenger nye biblioteker for å kjøre infrastrukturen.

Manuell installasjon i miljøer

I utgangspunktet installerte vi løsningen for miljøer manuelt. For å automatisere denne prosessen opprettet vi en CI/CD-pipeline. Vi valgte den kontinuerlige leveringsprosessen fordi kontinuerlig distribusjon ennå ikke er akseptabel for oss fra forretningsprosessers synspunkt. Derfor utføres sending for drift ved hjelp av en knapp, og for testing - automatisk.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Vi bruker Atlassian, Bitbucket for kildekodelagring og Bamboo for bygging. Vi liker å skrive byggeskript i Cake fordi det er det samme som C#. Ferdige pakker kommer til Artifactory, og Ansible kommer automatisk til testserverne, hvoretter de kan testes umiddelbart.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Separat loggføring


På et tidspunkt var en av ideene til monolitten å gi delt logging. Vi trengte også å forstå hva vi skal gjøre med de individuelle loggene som er på diskene. Loggene våre er skrevet til tekstfiler. Vi bestemte oss for å bruke en standard ELK-stabel. Vi skrev ikke direkte til ELK gjennom leverandørene, men bestemte oss for at vi skulle endre tekstloggene og skrive sporings-IDen i dem som en identifikator, og legge til tjenestenavnet, slik at disse loggene kunne analyseres senere.

Overgangen fra monolitt til mikrotjenester: historie og praksis

Ved å bruke Filebeat får vi muligheten til å samle loggene våre fra servere, for så å transformere dem, bruke Kibana til å bygge spørringer i brukergrensesnittet og se hvordan samtalen gikk mellom tjenestene. Trace ID hjelper mye med dette.

Testing og feilsøking relaterte tjenester


I utgangspunktet forsto vi ikke helt hvordan vi skulle feilsøke tjenestene som ble utviklet. Alt var enkelt med monolitten; vi kjørte den på en lokal maskin. Først prøvde de å gjøre det samme med mikrotjenester, men noen ganger for å starte en mikrotjeneste fullt ut, må du starte flere andre, og dette er upraktisk. Vi innså at vi må flytte til en modell der vi bare lar tjenesten eller tjenestene vi ønsker å feilsøke på den lokale maskinen. De resterende tjenestene brukes fra servere som matcher konfigurasjonen med prod. Etter feilsøking, under testing, for hver oppgave, blir bare de endrede tjenestene utstedt til testserveren. Dermed testes løsningen i den formen den vil fremstå i produksjon i fremtiden.

Det finnes servere som kun kjører produksjonsversjoner av tjenester. Disse serverne er nødvendige i tilfelle hendelser, for å sjekke levering før distribusjon og for intern opplæring.

Vi har lagt til en automatisert testprosess ved å bruke det populære Specflow-biblioteket. Tester kjøres automatisk med NUnit umiddelbart etter distribusjon fra Ansible. Hvis oppgavedekningen er helautomatisk, er det ikke behov for manuell testing. Selv om noen ganger ytterligere manuell testing fortsatt er nødvendig. Vi bruker tagger i Jira for å bestemme hvilke tester som skal kjøres for et spesifikt problem.

I tillegg har behovet for lasttesting økt; tidligere ble det kun utført i sjeldne tilfeller. Vi bruker JMeter til å kjøre tester, InfluxDB til å lagre dem, og Grafana til å bygge prosessgrafer.

Hva har vi oppnådd?


For det første ble vi kvitt konseptet "utgivelse". Borte er de to-måneders monstrøse utgivelsene da denne kolossen ble distribuert i et produksjonsmiljø, og midlertidig forstyrret forretningsprosesser. Nå distribuerer vi tjenester i gjennomsnitt hver 1,5 dag, og grupperer dem fordi de går i drift etter godkjenning.

Det er ingen fatale feil i systemet vårt. Hvis vi slipper en mikrotjeneste med en feil, vil funksjonaliteten knyttet til den bli ødelagt, og all annen funksjonalitet vil ikke bli påvirket. Dette forbedrer brukeropplevelsen betraktelig.

Vi kan kontrollere distribusjonsmønsteret. Du kan velge grupper av tjenester separat fra resten av løsningen, om nødvendig.

I tillegg har vi redusert problemet betraktelig med en stor kø av forbedringer. Vi har nå egne produktteam som jobber med noen av tjenestene selvstendig. Scrum-prosessen passer allerede godt her. Et spesifikt team kan ha en egen Produkteier som tildeler oppgaver til det.

Oppsummering

  • Mikrotjenester er godt egnet for å dekomponere komplekse systemer. I prosessen begynner vi å forstå hva som er i systemet vårt, hvilke begrensede sammenhenger det er, hvor grensene deres går. Dette lar deg distribuere forbedringer riktig mellom moduler og forhindre kodeforvirring.
  • Mikrotjenester gir organisatoriske fordeler. De blir ofte bare snakket om som arkitektur, men enhver arkitektur er nødvendig for å løse forretningsbehov, og ikke alene. Derfor kan vi si at mikrotjenester er godt egnet for å løse problemer i små team, gitt at Scrum er veldig populært nå.
  • Separasjon er en iterativ prosess. Du kan ikke ta en applikasjon og bare dele den inn i mikrotjenester. Det resulterende produktet er usannsynlig å være funksjonelt. Når du dedikerer mikrotjenester, er det fordelaktig å omskrive den eksisterende arven, det vil si gjøre den om til kode som vi liker og bedre møter forretningsbehov når det gjelder funksjonalitet og hastighet.

    Et lite forbehold: Kostnadene ved å flytte til mikrotjenester er ganske betydelige. Det tok lang tid å løse infrastrukturproblemet alene. Så hvis du har en liten applikasjon som ikke krever spesifikk skalering, med mindre du har et stort antall kunder som konkurrerer om teamets oppmerksomhet og tid, kan det hende at mikrotjenester ikke er det du trenger i dag. Det er ganske dyrt. Starter du prosessen med mikrotjenester, vil kostnadene i utgangspunktet være høyere enn om du starter samme prosjekt med utvikling av en monolitt.

    PS En mer emosjonell historie (og som for deg personlig) - iflg link.
    Her er den fullstendige versjonen av rapporten.

Kilde: www.habr.com

Legg til en kommentar