Overgang fra monolit til mikrotjenester: historie og praksis

I denne artikel vil jeg fortælle om, hvordan det projekt, jeg arbejder på, blev transformeret fra en stor monolit til et sæt mikrotjenester.

Projektet begyndte sin historie for ganske lang tid siden, i begyndelsen af ​​2000. De første versioner blev skrevet i Visual Basic 6. Med tiden blev det klart, at udvikling på dette sprog ville være vanskelig at understøtte i fremtiden, da IDE og selve sproget er dårligt udviklet. I slutningen af ​​2000'erne blev det besluttet at skifte til det mere lovende C#. Den nye version blev skrevet parallelt med revisionen af ​​den gamle, efterhånden blev der skrevet mere og mere kode i .NET. Backend i C# var oprindeligt fokuseret på en tjenestearkitektur, men under udviklingen blev der brugt fælles biblioteker med logik, og tjenester blev lanceret i en enkelt proces. Resultatet var en applikation, som vi kaldte en "servicemonolit."

En af de få fordele ved denne kombination var tjenesternes evne til at kalde hinanden gennem en ekstern API. Der var klare forudsætninger for overgangen til en mere korrekt service, og i fremtiden mikroservicearkitektur.

Vi startede vores arbejde med nedbrydning omkring 2015. Vi har endnu ikke nået en ideel tilstand – der er stadig dele af et stort projekt, som næppe kan kaldes monolitter, men de ligner heller ikke mikrotjenester. Ikke desto mindre er fremskridtene betydelige.
Jeg vil tale om det i artiklen.

Overgang fra monolit til mikrotjenester: historie og praksis

Indhold

Arkitektur og problemer med den eksisterende løsning


Oprindeligt så arkitekturen sådan ud: Brugergrænsefladen er en separat applikation, den monolitiske del er skrevet i Visual Basic 6, .NET-applikationen er et sæt relaterede tjenester, der arbejder med en ret stor database.

Ulemper ved den tidligere løsning

Enkelt point of failure
Vi havde et enkelt fejlpunkt: .NET-applikationen kørte i en enkelt proces. Hvis et modul fejlede, fejlede hele applikationen og måtte genstartes. Da vi automatiserer et stort antal processer for forskellige brugere, på grund af en fejl i en af ​​dem, kunne alle ikke arbejde i nogen tid. Og i tilfælde af en softwarefejl hjalp selv backup ikke.

Kø af forbedringer
Denne ulempe er ret organisatorisk. Vores applikation har mange kunder, og de ønsker alle at forbedre den så hurtigt som muligt. Tidligere var det umuligt at gøre dette parallelt, og alle kunder stod i kø. Denne proces var negativ for virksomheder, fordi de skulle bevise, at deres opgave var værdifuld. Og udviklingsteamet brugte tid på at organisere denne kø. Dette tog en masse tid og kræfter, og produktet kunne i sidste ende ikke ændre sig så hurtigt, som de ville have ønsket.

Suboptimal brug af ressourcer
Når vi hostede tjenester i en enkelt proces, kopierede vi altid konfigurationen fuldstændigt fra server til server. Vi ønskede at placere de mest belastede tjenester separat for ikke at spilde ressourcer og få mere fleksibel kontrol over vores implementeringsordning.

Svært at implementere moderne teknologier
Et problem, der er kendt for alle udviklere: der er et ønske om at introducere moderne teknologier i projektet, men der er ingen mulighed. Med en stor monolitisk løsning bliver enhver opdatering af det nuværende bibliotek, for ikke at nævne overgangen til et nyt, til en ret ikke-triviel opgave. Det tager lang tid at bevise over for holdlederen, at dette vil give flere bonusser end spildte nerver.

Svært ved at udstede ændringer
Dette var det mest alvorlige problem - vi udgav udgivelser hver anden måned.
Hver udgivelse blev til en virkelig katastrofe for banken på trods af udviklernes test og indsats. Virksomheden forstod, at i begyndelsen af ​​ugen ville nogle af dens funktionalitet ikke fungere. Og udviklerne forstod, at en uge med alvorlige hændelser ventede dem.
Alle havde et ønske om at ændre situationen.

Forventninger fra mikrotjenester


Udstedelse af komponenter, når de er klar. Levering af komponenter når de er klar ved at nedbryde opløsningen og adskille forskellige processer.

Små produktteams. Dette er vigtigt, fordi et stort team, der arbejdede på den gamle monolit, var svært at styre. Sådan et team var tvunget til at arbejde efter en stram proces, men de ønskede mere kreativitet og selvstændighed. Kun små hold havde råd til dette.

Isolering af tjenester i separate processer. Ideelt set ønskede jeg at isolere det i containere, men et stort antal tjenester skrevet i .NET Framework kører kun på Windows. Tjenester baseret på .NET Core dukker nu op, men der er få af dem endnu.

Implementeringsfleksibilitet. Vi vil gerne kombinere tjenester, som vi har brug for det, og ikke som kodeksen fremtvinger det.

Brug af nye teknologier. Dette er interessant for enhver programmør.

Overgangsproblemer


Selvfølgelig, hvis det var nemt at bryde en monolit op i mikrotjenester, ville der ikke være behov for at tale om det på konferencer og skrive artikler. Der er mange faldgruber i denne proces; jeg vil beskrive de vigtigste, der hindrede os.

Første problem typisk for de fleste monolitter: sammenhæng i forretningslogikken. Når vi skriver en monolit, vil vi gerne genbruge vores klasser for ikke at skrive unødvendig kode. Og når man flytter til mikrotjenester, bliver dette et problem: al koden er ret tæt koblet, og det er svært at adskille tjenesterne.

På tidspunktet for arbejdets start havde depotet mere end 500 projekter og mere end 700 tusind linjer kode. Dette er en ret stor beslutning og andet problem. Det var ikke muligt blot at tage det og opdele det i mikrotjenester.

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

Sådan går du fra monolit til mikrotjenester


Levering af mikrotjenester

For det første bestemte vi os selv for, at adskillelsen af ​​mikrotjenester er en iterativ proces. Vi var altid forpligtet til at udvikle forretningsproblemer sideløbende. Hvordan vi vil implementere dette teknisk er allerede vores problem. Derfor forberedte vi os på en iterativ proces. Det vil ikke fungere på nogen anden måde, hvis du har en stor ansøgning, og den i første omgang ikke er klar til at blive omskrevet.

Hvilke metoder bruger vi til at isolere mikrotjenester?

Den første vej — flytte eksisterende moduler som tjenester. I denne henseende var vi heldige: der var allerede registrerede tjenester, der fungerede ved hjælp af WCF-protokollen. De blev opdelt i separate forsamlinger. Vi porterede dem separat og tilføjede en lille launcher til hver build. Det blev skrevet ved hjælp af det vidunderlige Topshelf-bibliotek, som giver dig mulighed for at køre applikationen både som en tjeneste og som en konsol. Dette er praktisk til fejlretning, da der ikke kræves yderligere projekter i løsningen.

Tjenesterne var forbundet efter forretningslogik, da de brugte fælles assemblies og arbejdede med en fælles database. De kunne næppe kaldes mikrotjenester i deres rene form. Vi kunne dog levere disse tjenester separat, i forskellige processer. Alene dette gjorde det muligt at reducere deres indflydelse på hinanden, hvilket reducerede problemet med parallel udvikling og et enkelt point of failure.

Samling med værten er kun én linje kode i programklassen. Vi gemte arbejdet med Topshelf i en hjælpeklasse.

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

       }
    }
}

Den anden måde at allokere mikrotjenester på er: skabe dem til at løse nye problemer. Hvis monolitten samtidig ikke vokser, er dette allerede fremragende, hvilket betyder, at vi bevæger os i den rigtige retning. For at løse nye problemer forsøgte vi at oprette separate tjenester. Hvis der var en sådan mulighed, så skabte vi mere "kanoniske" tjenester, der fuldstændigt styrer deres egen datamodel, en separat database.

Vi startede, ligesom mange andre, med autentificerings- og autorisationstjenester. De er perfekte til dette. De er uafhængige, som regel har de en separat datamodel. De selv interagerer ikke med monolitten, kun det henvender sig til dem for at løse nogle problemer. Ved at bruge disse tjenester kan du begynde overgangen til en ny arkitektur, fejlsøge infrastrukturen på dem, prøve nogle tilgange relateret til netværksbiblioteker osv. Vi har ingen teams i vores organisation, der ikke kunne oprette en godkendelsestjeneste.

Den tredje måde at allokere mikrotjenester påDen vi bruger er lidt specifik for os. Dette er fjernelse af forretningslogik fra UI-laget. Vores vigtigste UI-applikation er desktop; den er ligesom backend skrevet i C#. Udviklerne lavede med jævne mellemrum fejl og overførte dele af logikken til brugergrænsefladen, som skulle have eksisteret i backend og blevet genbrugt.

Hvis du ser på et rigtigt eksempel fra koden til UI-delen, kan du se, at det meste af denne løsning indeholder ægte forretningslogik, der er nyttig i andre processer, ikke kun til opbygning af UI-formularen.

Overgang fra monolit til mikrotjenester: historie og praksis

Den rigtige UI-logik er der kun i de sidste par linjer. Vi overførte det til serveren, så det kunne genbruges, og derved reducerede brugergrænsefladen og opnåede den korrekte arkitektur.

Den fjerde og vigtigste måde at isolere mikrotjenester på, som gør det muligt at reducere monolitten, er fjernelse af eksisterende tjenester med behandling. Når vi udtager eksisterende moduler som de er, falder resultatet ikke altid til udviklernes smag, og forretningsprocessen kan være blevet forældet siden funktionaliteten blev oprettet. Med refactoring kan vi understøtte en ny forretningsproces, fordi forretningskravene konstant ændrer sig. Vi kan forbedre kildekoden, fjerne kendte defekter og skabe en bedre datamodel. Der kommer mange fordele.

At adskille tjenester fra behandling er uløseligt forbundet med begrebet afgrænset kontekst. Dette er et koncept fra Domain Driven Design. Det betyder en del af domænemodellen, hvor alle vilkårene for et enkelt sprog er unikt defineret. Lad os se på sammenhængen med forsikring og regninger som et eksempel. Vi har en monolitisk ansøgning, og vi skal arbejde med kontoen i forsikring. Vi forventer, at udvikleren finder en eksisterende kontoklasse i en anden samling, refererer til den fra forsikringsklassen, og vi har en arbejdskode. DRY princippet vil blive respekteret, opgaven vil blive udført hurtigere ved at bruge eksisterende kode.

Som følge heraf viser det sig, at sammenhænge mellem konti og forsikring hænger sammen. Efterhånden som nye krav dukker op, vil denne kobling forstyrre udviklingen og øge kompleksiteten af ​​allerede kompleks forretningslogik. For at løse dette problem skal du finde grænserne mellem kontekster i koden og fjerne deres overtrædelser. For eksempel er det i forsikringssammenhæng meget muligt, at et 20-cifret centralbankkontonummer og den dato, kontoen blev åbnet, vil være tilstrækkeligt.

For at adskille disse afgrænsede kontekster fra hinanden og begynde processen med at adskille mikrotjenester fra en monolitisk løsning, brugte vi en tilgang som at skabe eksterne API'er i applikationen. Hvis vi vidste, at et eller andet modul skulle blive en mikrotjeneste, på en eller anden måde modificeret i processen, så ringede vi straks til logikken, der hører til en anden begrænset kontekst, gennem eksterne opkald. For eksempel via REST eller WCF.

Vi besluttede bestemt, at vi ikke ville undgå kode, der ville kræve distribuerede transaktioner. I vores tilfælde viste det sig at være ret nemt at følge denne regel. Vi er endnu ikke stødt på situationer, hvor der virkelig er brug for strengt distribuerede transaktioner - den endelige sammenhæng mellem modulerne er ganske tilstrækkelig.

Lad os se på et specifikt eksempel. Vi har konceptet med en orkestrator - en pipeline, der behandler entiteten af ​​"applikationen". Han opretter en klient, en konto og et bankkort på skift. Hvis klienten og kontoen er oprettet med succes, men oprettelsen af ​​kortet mislykkes, flytter applikationen ikke til statussen "vellykket" og forbliver i statussen "kortet ikke oprettet". I fremtiden vil baggrundsaktivitet samle det op og afslutte det. Systemet har været i en tilstand af inkonsekvens i nogen tid, men vi er generelt tilfredse med dette.

Hvis der opstår en situation, hvor det er nødvendigt konsekvent at gemme en del af dataene, vil vi højst sandsynligt gå efter konsolidering af tjenesten for at behandle den i én proces.

Lad os se på et eksempel på tildeling af en mikrotjeneste. Hvordan kan du bringe det til produktion relativt sikkert? I dette eksempel har vi en separat del af systemet - et lønservicemodul, som vi gerne vil lave mikroservice af et af kodesektionerne.

Overgang fra monolit til mikrotjenester: historie og praksis

Først og fremmest opretter vi en mikrotjeneste ved at omskrive koden. Vi er ved at forbedre nogle aspekter, som vi ikke var tilfredse med. Vi implementerer nye forretningskrav fra kunden. Vi tilføjer en API Gateway til forbindelsen mellem brugergrænsefladen og backend, som vil sørge for viderestilling af opkald.

Overgang fra monolit til mikrotjenester: historie og praksis

Dernæst frigiver vi denne konfiguration i drift, men i en pilottilstand. De fleste af vores brugere arbejder stadig med gamle forretningsprocesser. For nye brugere udvikler vi en ny version af den monolitiske applikation, der ikke længere indeholder denne proces. Grundlæggende har vi en kombination af en monolit og en mikroservice, der arbejder som pilot.

Overgang fra monolit til mikrotjenester: historie og praksis

Med en vellykket pilot forstår vi, at den nye konfiguration faktisk er brugbar, vi kan fjerne den gamle monolit fra ligningen og lade den nye konfiguration blive i stedet for den gamle løsning.

Overgang fra monolit til mikrotjenester: historie og praksis

I alt bruger vi næsten alle eksisterende metoder til at opdele kildekoden til en monolit. Alle af dem giver os mulighed for at reducere størrelsen af ​​dele af applikationen og oversætte dem til nye biblioteker, hvilket giver bedre kildekode.

Arbejde med databasen


Databasen kan opdeles værre end kildekoden, da den ikke kun indeholder det aktuelle skema, men også akkumulerede historiske data.

Vores database havde ligesom mange andre en anden vigtig ulempe - dens enorme størrelse. Denne database blev designet i overensstemmelse med den indviklede forretningslogik af en monolit, og relationer akkumulerede mellem tabellerne i forskellige afgrænsede kontekster.

I vores tilfælde opstod et problem, der opstår i mange store projekter, for at toppe alle problemerne (stor database, mange forbindelser, nogle gange uklare grænser mellem tabeller: brugen af ​​den delte databaseskabelon. Data blev taget fra tabeller gennem visning, gennem replikering og sendt til andre systemer, hvor denne replikering var nødvendig. Som et resultat kunne vi ikke flytte tabellerne ind i et separat skema, fordi de blev brugt aktivt.

Den samme opdeling i begrænsede sammenhænge i koden hjælper os i adskillelse. Det giver os normalt en ret god idé om, hvordan vi opdeler dataene på databaseniveau. Vi forstår hvilke tabeller der hører til en afgrænset kontekst og hvilke til en anden.

Vi brugte to globale metoder til databasepartitionering: partitionering af eksisterende tabeller og partitionering med behandling.

Opsplitning af eksisterende tabeller er en god metode at bruge, hvis datastrukturen er god, opfylder forretningskrav, og alle er glade for den. I dette tilfælde kan vi adskille eksisterende tabeller i et separat skema.

Der skal en afdeling med forarbejdning til, når forretningsmodellen har ændret sig meget, og tabellerne slet ikke længere tilfredsstiller os.

Opdeling af eksisterende tabeller. Vi er nødt til at bestemme, hvad vi vil adskille. Uden denne viden vil intet fungere, og her vil adskillelsen af ​​afgrænsede sammenhænge i koden hjælpe os. Hvis du kan forstå grænserne for sammenhænge i kildekoden, bliver det som regel tydeligt, hvilke tabeller der skal medtages på listen for afdelingen.

Lad os forestille os, at vi har en løsning, hvor to monolitmoduler interagerer med én database. Vi skal sikre os, at kun et modul interagerer med sektionen af ​​adskilte tabeller, og det andet begynder at interagere med det via API'en. Til at begynde med er det nok, at kun optagelse udføres gennem API'en. Dette er en nødvendig betingelse for, at vi kan tale om mikrotjenesters uafhængighed. Læseforbindelser kan forblive, så længe der ikke er noget stort problem.

Overgang fra monolit til mikrotjenester: historie og praksis

Det næste trin er, at vi kan adskille sektionen af ​​kode, der fungerer med adskilte tabeller, med eller uden behandling, i en separat mikrotjeneste og køre den i en separat proces, en container. Dette vil være en separat tjeneste med en forbindelse til monolitdatabasen og de tabeller, der ikke relaterer direkte til den. Monolitten interagerer stadig til læsning med den aftagelige del.

Overgang fra monolit til mikrotjenester: historie og praksis

Senere vil vi fjerne denne forbindelse, det vil sige, at læsning af data fra en monolitisk applikation fra adskilte tabeller vil også blive overført til API'en.

Overgang fra monolit til mikrotjenester: historie og praksis

Dernæst vil vi fra den generelle database vælge de tabeller, som kun den nye mikrotjeneste fungerer med. Vi kan flytte tabellerne til et separat skema eller endda til en separat fysisk database. Der er stadig en læseforbindelse mellem mikrotjenesten og monolitdatabasen, men der er ikke noget at bekymre sig om, i denne konfiguration kan den leve i ret lang tid.

Overgang fra monolit til mikrotjenester: historie og praksis

Det sidste trin er at fjerne alle forbindelser fuldstændigt. I dette tilfælde skal vi muligvis migrere data fra hoveddatabasen. Nogle gange ønsker vi at genbruge nogle data eller mapper replikeret fra eksterne systemer i flere databaser. Dette sker for os med jævne mellemrum.

Overgang fra monolit til mikrotjenester: historie og praksis

Bearbejdningsafdeling. Denne metode ligner meget den første, kun i omvendt rækkefølge. Vi tildeler straks en ny database og en ny mikroservice, der interagerer med monolitten via en API. Men samtidig er der stadig et sæt databasetabeller, som vi ønsker at slette i fremtiden. Vi har ikke længere brug for det, vi erstattede det i den nye model.

Overgang fra monolit til mikrotjenester: historie og praksis

For at denne ordning skal fungere, har vi sandsynligvis brug for en overgangsperiode.

Der er så to mulige tilgange.

Første: vi dublerer alle data i de nye og gamle databaser. I dette tilfælde har vi dataredundans, og der kan opstå synkroniseringsproblemer. Men vi kan tage to forskellige klienter. Den ene vil arbejde med den nye version, den anden med den gamle.

Second: vi opdeler dataene efter nogle forretningskriterier. F.eks. havde vi 5 produkter i systemet, som var gemt i den gamle database. Vi placerer den sjette inden for den nye forretningsopgave i en ny database. Men vi skal bruge en API-gateway, der synkroniserer disse data og viser klienten, hvor og hvad den skal komme fra.

Begge tilgange virker, vælg afhængigt af situationen.

Når vi er sikre på, at alt fungerer, kan den del af monolitten, der fungerer med gamle databasestrukturer, deaktiveres.

Overgang fra monolit til mikrotjenester: historie og praksis

Det sidste trin er at fjerne de gamle datastrukturer.

Overgang fra monolit til mikrotjenester: historie og praksis

For at opsummere kan vi sige, at vi har problemer med databasen: det er svært at arbejde med det i forhold til kildekoden, det er sværere at dele, men det kan og bør gøres. Vi har fundet nogle måder, der giver os mulighed for at gøre dette ganske sikkert, men det er stadig nemmere at lave fejl med data end med kildekode.

Arbejde med kildekode


Sådan så kildekodediagrammet ud, da vi begyndte at analysere det monolitiske projekt.

Overgang fra monolit til mikrotjenester: historie og praksis

Den kan groft opdeles i tre lag. Dette er et lag af lancerede moduler, plugins, tjenester og individuelle aktiviteter. Faktisk var disse indgangspunkter inden for en monolitisk løsning. Alle var tæt forseglet med et fælles lag. Det havde forretningslogik, at tjenesterne delte og en masse forbindelser. Hver service og plugin brugte op til 10 eller flere almindelige samlinger, afhængigt af deres størrelse og udviklernes samvittighed.

Vi var heldige at have infrastrukturbiblioteker, der kunne bruges separat.

Nogle gange opstod der en situation, hvor nogle almindelige objekter faktisk ikke tilhørte dette lag, men var infrastrukturbiblioteker. Dette blev løst ved at omdøbe.

Den største bekymring var afgrænsede sammenhænge. Det skete, at 3-4 sammenhænge blev blandet i én Fællesforsamling og brugte hinanden inden for de samme forretningsfunktioner. Det var nødvendigt at forstå, hvor dette kunne opdeles og langs hvilke grænser, og hvad man derefter skulle gøre med at kortlægge denne opdeling i kildekodesamlinger.

Vi har formuleret flere regler for kodeopdelingsprocessen.

Den første: Vi ønskede ikke længere at dele forretningslogik mellem tjenester, aktiviteter og plugins. Vi ønskede at gøre forretningslogik uafhængig inden for mikrotjenester. Mikrotjenester er på den anden side ideelt set tænkt som tjenester, der eksisterer helt uafhængigt. Jeg mener, at denne tilgang er noget spild, og den er svær at opnå, fordi for eksempel tjenester i C# under alle omstændigheder vil være forbundet af et standardbibliotek. Vores system er skrevet i C#; vi har endnu ikke brugt andre teknologier. Derfor besluttede vi, at vi havde råd til at bruge fælles tekniske forsamlinger. Det vigtigste er, at de ikke indeholder fragmenter af forretningslogik. Hvis du har en bekvemmelighedsindpakning over den ORM, du bruger, er det meget dyrt at kopiere det fra tjeneste til tjeneste.

Vores team er fan af domænedrevet design, så løgarkitektur passede perfekt til os. Grundlaget for vores tjenester er ikke dataadgangslaget, men en samling med domænelogik, som kun indeholder forretningslogik og ikke har forbindelse til infrastrukturen. Samtidig kan vi selvstændigt modificere domænesamlingen for at løse problemer relateret til rammer.

På dette tidspunkt stødte vi på vores første alvorlige problem. Tjenesten skulle henvise til én domænesamling, vi ville gøre logikken uafhængig, og DRY-princippet hæmmede os i høj grad her. Udviklerne ønskede at genbruge klasser fra tilstødende forsamlinger for at undgå duplikering, og som et resultat begyndte domæner at blive linket sammen igen. Vi analyserede resultaterne og besluttede, at problemet måske også ligger i området for kildekodelagringsenheden. Vi havde et stort lager, der indeholdt al kildekoden. Løsningen for hele projektet var meget vanskelig at samle på en lokal maskine. Derfor blev der lavet separate små løsninger til dele af projektet, og ingen forbød at tilføje nogle fælles- eller domænesamlinger til dem og genbruge dem. Det eneste værktøj, der ikke tillod os at gøre dette, var kodegennemgang. Men nogle gange mislykkedes det også.

Så begyndte vi at gå over til en model med separate depoter. Forretningslogikken flyder ikke længere fra tjeneste til tjeneste, domæner er virkelig blevet uafhængige. Afgrænsede sammenhænge understøttes tydeligere. Hvordan genbruger vi infrastrukturbiblioteker? Vi adskilte dem i et separat depot og lagde dem derefter i Nuget-pakker, som vi lagde i Artifactory. Ved enhver ændring sker montering og offentliggørelse automatisk.

Overgang fra monolit til mikrotjenester: historie og praksis

Vores tjenester begyndte at referere til interne infrastrukturpakker på samme måde som eksterne. Vi downloader eksterne biblioteker fra Nuget. For at arbejde med Artifactory, hvor vi placerede disse pakker, brugte vi to pakkeadministratorer. I små depoter brugte vi også Nuget. I repositories med flere tjenester brugte vi Paket, som giver mere versionskonsistens mellem moduler.

Overgang fra monolit til mikrotjenester: historie og praksis

Ved at arbejde på kildekoden, ændre lidt i arkitekturen og adskille lagrene gør vi vores tjenester mere uafhængige.

Infrastrukturproblemer


De fleste af ulemperne ved at flytte til mikrotjenester er infrastrukturrelaterede. Du skal bruge automatiseret implementering, du skal bruge nye biblioteker til at køre infrastrukturen.

Manuel installation i miljøer

I første omgang installerede vi løsningen til miljøer manuelt. For at automatisere denne proces oprettede vi en CI/CD-pipeline. Vi valgte den kontinuerlige leveringsproces, fordi kontinuerlig implementering endnu ikke er acceptabel for os set fra forretningsprocessernes synspunkt. Derfor udføres afsendelse til drift ved hjælp af en knap og til test - automatisk.

Overgang fra monolit til mikrotjenester: historie og praksis

Vi bruger Atlassian, Bitbucket til kildekodelagring og Bamboo til bygning. Vi kan godt lide at skrive build-scripts i Cake, fordi det er det samme som C#. Der kommer færdige pakker til Artifactory, og Ansible kommer automatisk til testserverne, hvorefter de kan testes med det samme.

Overgang fra monolit til mikrotjenester: historie og praksis

Separat logning


På et tidspunkt var en af ​​ideerne med monolitten at levere delt logning. Vi havde også brug for at forstå, hvad vi skulle gøre med de individuelle logfiler, der er på diskene. Vores logs er skrevet til tekstfiler. Vi besluttede at bruge en standard ELK-stak. Vi skrev ikke direkte til ELK gennem udbyderne, men besluttede, at vi ville ændre tekstlogfilerne og skrive sporings-id'et i dem som en identifikator og tilføje servicenavnet, så disse logfiler kunne parses senere.

Overgang fra monolit til mikrotjenester: historie og praksis

Ved at bruge Filebeat får vi mulighed for at indsamle vores logfiler fra servere, derefter transformere dem, bruge Kibana til at bygge forespørgsler i brugergrænsefladen og se, hvordan opkaldet gik mellem tjenester. Trace ID hjælper meget med dette.

Test og fejlfinding relaterede tjenester


Til at begynde med forstod vi ikke helt, hvordan vi skulle fejlfinde de tjenester, der blev udviklet. Alt var enkelt med monolitten; vi kørte den på en lokal maskine. Først forsøgte de at gøre det samme med mikrotjenester, men nogle gange for fuldt ud at starte en mikrotjeneste skal du starte flere andre, og det er ubelejligt. Vi indså, at vi er nødt til at flytte til en model, hvor vi kun efterlader den eller de tjenester, som vi ønsker at fejlfinde på den lokale maskine. De resterende tjenester bruges fra servere, der matcher konfigurationen med prod. Efter debugging, under test, for hver opgave, udstedes kun de ændrede tjenester til testserveren. Dermed testes løsningen i den form, den vil fremstå i produktionen i fremtiden.

Der er servere, der kun kører produktionsversioner af tjenester. Disse servere er nødvendige i tilfælde af hændelser, for at kontrollere levering før implementering og til intern træning.

Vi har tilføjet en automatiseret testproces ved hjælp af det populære Specflow-bibliotek. Test kører automatisk ved hjælp af NUnit umiddelbart efter implementering fra Ansible. Hvis opgavedækningen er fuldautomatisk, er der ikke behov for manuel test. Selvom der nogle gange er yderligere manuel testning stadig påkrævet. Vi bruger tags i Jira til at bestemme, hvilke tests der skal køres for et specifikt problem.

Derudover er behovet for belastningstest steget; tidligere blev det kun udført i sjældne tilfælde. Vi bruger JMeter til at køre test, InfluxDB til at gemme dem og Grafana til at bygge procesgrafer.

Hvad har vi opnået?


For det første slap vi af med begrebet "frigivelse". De to måneder lange monstrøse udgivelser er forbi, da denne kolos blev installeret i et produktionsmiljø, hvilket midlertidigt forstyrrede forretningsprocesser. Nu implementerer vi tjenester i gennemsnit hver 1,5 dag, og grupperer dem, fordi de går i drift efter godkendelse.

Der er ingen fatale fejl i vores system. Hvis vi frigiver en mikrotjeneste med en fejl, vil den funktionalitet, der er knyttet til den, blive ødelagt, og al anden funktionalitet vil ikke blive påvirket. Dette forbedrer brugeroplevelsen markant.

Vi kan kontrollere implementeringsmønstret. Du kan vælge grupper af tjenester separat fra resten af ​​løsningen, hvis det er nødvendigt.

Derudover har vi reduceret problemet markant med en stor kø af forbedringer. Vi har nu separate produktteams, der arbejder selvstændigt med nogle af tjenesterne. Scrum-processen passer allerede godt her. Et specifikt team kan have en separat Product Owner, som tildeler opgaver til det.

Resumé

  • Mikrotjenester er velegnede til at nedbryde komplekse systemer. I processen begynder vi at forstå, hvad der er i vores system, hvilke begrænsede sammenhænge der er, hvor deres grænser går. Dette giver dig mulighed for korrekt at distribuere forbedringer mellem moduler og forhindre kodeforvirring.
  • Mikrotjenester giver organisatoriske fordele. De omtales ofte kun som arkitektur, men enhver arkitektur er nødvendig for at løse forretningsbehov og ikke alene. Derfor kan vi sige, at mikrotjenester er velegnede til at løse problemer i små teams, da Scrum er meget populært nu.
  • Adskillelse er en iterativ proces. Du kan ikke tage en applikation og blot opdele den i mikrotjenester. Det er usandsynligt, at det resulterende produkt er funktionelt. Når man dedikerer mikrotjenester, er det fordelagtigt at omskrive den eksisterende arv, det vil sige, omdanne den til kode, som vi kan lide og bedre opfylder forretningsbehov i form af funktionalitet og hastighed.

    En lille advarsel: Omkostningerne ved at flytte til mikrotjenester er ret betydelige. Det tog lang tid at løse infrastrukturproblemet alene. Så hvis du har en lille applikation, der ikke kræver specifik skalering, medmindre du har et stort antal kunder, der konkurrerer om dit teams opmærksomhed og tid, så er mikrotjenester måske ikke det, du har brug for i dag. Det er ret dyrt. Starter man processen med microservices, så vil omkostningerne i første omgang være højere, end hvis man starter det samme projekt med udvikling af en monolit.

    PS En mere følelsesladet historie (og som om for dig personligt) - iflg link.
    Her er den fulde version af rapporten.

Kilde: www.habr.com

Tilføj en kommentar