Dela via


Utforma ett mikrotjänstorienterat program

Dricks

Det här innehållet är ett utdrag från eBook, .NET Microservices Architecture for Containerized .NET Applications, tillgängligt på .NET Docs eller som en kostnadsfri nedladdningsbar PDF som kan läsas offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Det här avsnittet fokuserar på att utveckla ett hypotetiskt företagsprogram på serversidan.

Programspecifikationer

Det hypotetiska programmet hanterar begäranden genom att köra affärslogik, komma åt databaser och sedan returnera HTML-, JSON- eller XML-svar. Vi säger att programmet måste ha stöd för olika klienter, inklusive skrivbordswebbläsare som kör ensidesprogram (SPA), traditionella webbappar, mobilappar och interna mobilappar. Programmet kan också exponera ett API för tredje part att använda. Den bör också kunna integrera sina mikrotjänster eller externa program asynkront, så att metoden hjälper till att återställa mikrotjänsterna vid partiella fel.

Programmet består av följande typer av komponenter:

  • Presentationskomponenter. Dessa komponenter ansvarar för att hantera användargränssnittet och använda fjärrtjänster.

  • Domän- eller affärslogik. Den här komponenten är programmets domänlogik.

  • Logik för databasåtkomst. Den här komponenten består av dataåtkomstkomponenter som ansvarar för åtkomst till databaser (SQL eller NoSQL).

  • Logik för programintegrering. Den här komponenten innehåller en meddelandekanal som baseras på meddelandeköer.

Programmet kräver hög skalbarhet, samtidigt som dess lodräta undersystem kan skalas ut autonomt, eftersom vissa undersystem kräver mer skalbarhet än andra.

Programmet måste kunna distribueras i flera infrastrukturmiljöer (flera offentliga moln och lokalt) och bör helst vara plattformsoberoende och enkelt kunna flytta från Linux till Windows (eller vice versa).

Utvecklingsteamkontext

Vi antar också följande om utvecklingsprocessen för programmet:

  • Du har flera utvecklingsteam som fokuserar på olika affärsområden i programmet.

  • Nya teammedlemmar måste bli produktiva snabbt och programmet måste vara enkelt att förstå och ändra.

  • Programmet kommer att ha en långsiktig utveckling och ständigt föränderliga affärsregler.

  • Du behöver god långsiktig underhållbarhet, vilket innebär att du har flexibilitet när du implementerar nya ändringar i framtiden samtidigt som du kan uppdatera flera undersystem med minsta möjliga påverkan på de andra undersystemen.

  • Du vill öva på kontinuerlig integrering och kontinuerlig distribution av programmet.

  • Du vill dra nytta av nya tekniker (ramverk, programmeringsspråk osv.) när du utvecklar programmet. Du vill inte göra fullständiga migreringar av programmet när du övergår till ny teknik, eftersom det skulle leda till höga kostnader och påverka programmets förutsägbarhet och stabilitet.

Välja en arkitektur

Vad ska programdistributionsarkitekturen vara? Specifikationerna för programmet, tillsammans med utvecklingskontexten, tyder starkt på att du bör utforma programmet genom att dela upp det i autonoma undersystem i form av samarbete mellan mikrotjänster och containrar , där en mikrotjänst är en container.

I den här metoden implementerar varje tjänst (container) en uppsättning sammanhängande och snävt relaterade funktioner. Ett program kan till exempel bestå av tjänster som katalogtjänsten, beställningstjänsten, korgtjänsten, användarprofiltjänsten osv.

Mikrotjänster kommunicerar med protokoll som HTTP (REST), men även asynkront (till exempel med AMQP) när det är möjligt, särskilt när uppdateringar sprids med integrationshändelser.

Mikrotjänster utvecklas och distribueras som containrar oberoende av varandra. Den här metoden innebär att ett utvecklingsteam kan utveckla och distribuera en viss mikrotjänst utan att påverka andra undersystem.

Varje mikrotjänst har en egen databas, vilket gör att den kan frikopplas helt från andra mikrotjänster. Vid behov uppnås konsekvens mellan databaser från olika mikrotjänster med hjälp av integreringshändelser på programnivå (via en logisk händelsebuss), enligt CQRS (Command and Query Responsibility Segregation). På grund av detta måste affärsbegränsningarna omfatta eventuell konsekvens mellan flera mikrotjänster och relaterade databaser.

eShopOnContainers: Ett referensprogram för .NET och mikrotjänster som distribueras med containrar

Så att du kan fokusera på arkitekturen och teknikerna i stället för att tänka på en hypotetisk affärsdomän som du kanske inte vet, har vi valt en välkänd affärsdomän , nämligen ett förenklat e-handelsprogram (e-shop) som presenterar en produktkatalog, tar beställningar från kunder, verifierar inventering och utför andra affärsfunktioner. Den här containerbaserade programkällan är tillgänglig på GitHub-lagringsplatsen eShopOnContainers .

Programmet består av flera undersystem, inklusive flera klientdelar för butiksgränssnittet (ett webbprogram och en intern mobilapp), tillsammans med serverdelsmikrotjänster och containrar för alla nödvändiga åtgärder på serversidan med flera API Gateways som konsoliderade startpunkter till de interna mikrotjänsterna. Bild 6–1 visar referensprogrammets arkitektur.

Diagram of client apps using eShopOnContainers in a single Docker host.

Bild 6-1. eShopOnContainers refererar till programarkitekturen för utvecklingsmiljön

Diagrammet ovan visar att Mobile- och SPA-klienter kommunicerar med enskilda API Gateway-slutpunkter som sedan kommunicerar med mikrotjänster. Traditionella webbklienter kommunicerar med MVC-mikrotjänster som kommunicerar med mikrotjänster via API-gatewayen.

Värdmiljö. I bild 6–1 ser du flera containrar som distribuerats i en enda Docker-värd. Det skulle vara fallet när du distribuerar till en enskild Docker-värd med kommandot docker-compose up. Men om du använder ett orchestrator- eller containerkluster kan varje container köras i en annan värd (nod) och alla noder kan köra valfritt antal containrar, som vi förklarade tidigare i arkitekturavsnittet.

Kommunikationsarkitektur. eShopOnContainers-programmet använder två kommunikationstyper, beroende på vilken typ av funktionell åtgärd (frågor kontra uppdateringar och transaktioner):

  • Http-klient-till-mikrotjänst-kommunikation via API Gateways. Den här metoden används för frågor och när du accepterar uppdaterings- eller transaktionskommandon från klientapparna. Metoden med API Gateways beskrivs i detalj i senare avsnitt.

  • Asynkron händelsebaserad kommunikation. Den här kommunikationen sker via en händelsebuss för att sprida uppdateringar över mikrotjänster eller för att integrera med externa program. Händelsebussen kan implementeras med valfri infrastrukturteknik för meddelandekoordinatorer som RabbitMQ eller med hjälp av servicebussar på högre nivå (abstraktionsnivå) som Azure Service Bus, NServiceBus, MassTransit eller Brighter.

Programmet distribueras som en uppsättning mikrotjänster i form av containrar. Klientappar kan kommunicera med de mikrotjänster som körs som containrar via de offentliga URL:er som publicerats av API Gateways.

Datasuveränitet per mikrotjänst

I exempelprogrammet äger varje mikrotjänst sin egen databas eller datakälla, även om alla SQL Server-databaser distribueras som en enda container. Det här designbeslutet fattades bara för att göra det enkelt för en utvecklare att hämta koden från GitHub, klona den och öppna den i Visual Studio eller Visual Studio Code. Alternativt gör det det enkelt att kompilera anpassade Docker-avbildningar med hjälp av .NET CLI och Docker CLI och sedan distribuera och köra dem i en Docker-utvecklingsmiljö. Hur som helst, med hjälp av containrar för datakällor kan utvecklare skapa och distribuera på några minuter utan att behöva etablera en extern databas eller någon annan datakälla med hårda beroenden för infrastruktur (moln eller lokalt).

I en verklig produktionsmiljö, för hög tillgänglighet och för skalbarhet, bör databaserna baseras på databasservrar i molnet eller lokalt, men inte i containrar.

Därför är distributionsenheterna för mikrotjänster (och även för databaser i det här programmet) Docker-containrar, och referensprogrammet är ett program med flera containrar som omfattar principer för mikrotjänster.

Ytterligare resurser

Fördelar med en mikrotjänstbaserad lösning

En mikrotjänstbaserad lösning som den här har många fördelar:

Varje mikrotjänst är relativt liten – lätt att hantera och utveckla. Specifikt:

  • Det är enkelt för en utvecklare att förstå och komma igång snabbt med god produktivitet.

  • Containrar startar snabbt, vilket gör utvecklare mer produktiva.

  • En IDE som Visual Studio kan läsa in mindre projekt snabbt, vilket gör utvecklare produktiva.

  • Varje mikrotjänst kan utformas, utvecklas och distribueras oberoende av andra mikrotjänster, vilket ger flexibilitet eftersom det är enklare att distribuera nya versioner av mikrotjänster ofta.

Det går att skala ut enskilda områden i programmet. Katalogtjänsten eller korgtjänsten kan till exempel behöva skalas ut, men inte beställningsprocessen. En infrastruktur för mikrotjänster blir mycket effektivare när det gäller de resurser som används vid utskalning än vad en monolitisk arkitektur skulle vara.

Du kan dela upp utvecklingsarbetet mellan flera team. Varje tjänst kan ägas av ett enda utvecklingsteam. Varje team kan hantera, utveckla, distribuera och skala sin tjänst oberoende av resten av teamen.

Problemen är mer isolerade. Om det finns ett problem i en tjänst påverkas endast den tjänsten från början (förutom när fel design används, med direkta beroenden mellan mikrotjänster) och andra tjänster kan fortsätta att hantera begäranden. Däremot kan en komponent som inte fungerar i en monolitisk distributionsarkitektur få ner hela systemet, särskilt när det gäller resurser, till exempel en minnesläcka. När ett problem i en mikrotjänst har lösts kan du dessutom distribuera bara den berörda mikrotjänsten utan att påverka resten av programmet.

Du kan använda de senaste teknikerna. Eftersom du kan börja utveckla tjänster separat och köra dem sida vid sida (tack vare containrar och .NET) kan du börja använda de senaste teknikerna och ramverken på ett lämpligt sätt i stället för att fastna på en äldre stack eller ett ramverk för hela programmet.

Nackdelar med en mikrotjänstbaserad lösning

En mikrotjänstbaserad lösning som den här har också några nackdelar:

Distribuerat program. Att distribuera programmet ökar komplexiteten för utvecklare när de utformar och skapar tjänsterna. Utvecklare måste till exempel implementera kommunikation mellan tjänster med hjälp av protokoll som HTTP eller AMQP, vilket ökar komplexiteten för testning och undantagshantering. Det lägger också till svarstid i systemet.

Distributionskomplexitet. Ett program som har dussintals typer av mikrotjänster och som behöver hög skalbarhet (det måste kunna skapa många instanser per tjänst och balansera dessa tjänster över många värdar) innebär en hög grad av distributionskomplexitet för IT-drift och hantering. Om du inte använder en mikrotjänstorienterad infrastruktur (till exempel en orkestrerare och schemaläggare) kan den ytterligare komplexiteten kräva mycket mer utvecklingsarbete än själva affärsprogrammet.

Atomiska transaktioner. Atomiska transaktioner mellan flera mikrotjänster är vanligtvis inte möjliga. Affärskraven måste omfatta eventuell konsekvens mellan flera mikrotjänster. Mer information finns i utmaningarna med idempotent meddelandebearbetning.

Ökade globala resursbehov (totalt minne, enheter och nätverksresurser för alla servrar eller värdar). När du i många fall ersätter ett monolitiskt program med en mikrotjänstmetod blir mängden initiala globala resurser som behövs av det nya mikrotjänstbaserade programmet större än infrastrukturbehoven för det ursprungliga monolitiska programmet. Den här metoden beror på att den högre graden av kornighet och distribuerade tjänster kräver mer globala resurser. Men med tanke på de låga kostnaderna för resurser i allmänhet och fördelen med att kunna skala ut vissa delar av programmet jämfört med långsiktiga kostnader vid utveckling av monolitiska program, är den ökade användningen av resurser vanligtvis en bra kompromiss för stora, långsiktiga program.

Problem med direkt kommunikation mellan klienter och mikrotjänster. När programmet är stort, med dussintals mikrotjänster, finns det utmaningar och begränsningar om programmet kräver direkt kommunikation från klient till mikrotjänst. Ett problem är ett potentiellt matchningsfel mellan klientens behov och de API:er som exponeras av var och en av mikrotjänsterna. I vissa fall kan klientprogrammet behöva göra många separata begäranden för att skapa användargränssnittet, vilket kan vara ineffektivt via Internet och skulle vara opraktiskt över ett mobilt nätverk. Därför bör begäranden från klientprogrammet till serverdelssystemet minimeras.

Ett annat problem med direkt kommunikation mellan klienter och mikrotjänster är att vissa mikrotjänster kanske använder protokoll som inte är webbvänliga. En tjänst kan använda ett binärt protokoll, medan en annan tjänst kan använda AMQP-meddelanden. Dessa protokoll är inte brandväggsvänliga och används bäst internt. Vanligtvis bör ett program använda protokoll som HTTP och WebSockets för kommunikation utanför brandväggen.

En annan nackdel med denna direkta klient-till-tjänst-metod är att det gör det svårt att omstrukturera kontrakten för dessa mikrotjänster. Med tiden kanske utvecklare vill ändra hur systemet partitioneras till tjänster. De kan till exempel slå samman två tjänster eller dela upp en tjänst i två eller flera tjänster. Men om klienter kommunicerar direkt med tjänsterna kan den här typen av refaktorisering bryta kompatibiliteten med klientappar.

Som nämnts i arkitekturavsnittet kan du när du utformar och skapar ett komplext program baserat på mikrotjänster överväga att använda flera detaljerade API Gateways i stället för den enklare direkta kommunikationen mellan klienter och mikrotjänster.

Partitionering av mikrotjänster. Slutligen, oavsett vilken metod du använder för din mikrotjänstarkitektur, är en annan utmaning att bestämma hur du ska partitionera ett program från slutpunkt till slutpunkt i flera mikrotjänster. Som du ser i arkitekturavsnittet i guiden finns det flera tekniker och metoder som du kan använda. I grund och botten måste du identifiera områden i programmet som är frikopplade från de andra områdena och som har ett lågt antal hårda beroenden. I många fall är den här metoden anpassad till partitionering av tjänster efter användningsfall. I vårt e-shop-program har vi till exempel en beställningstjänst som ansvarar för all affärslogik som är relaterad till orderprocessen. Vi har även katalogtjänsten och korgtjänsten som implementerar andra funktioner. Helst bör varje tjänst bara ha en liten uppsättning ansvarsområden. Den här metoden liknar principen för enskilt ansvar (SRP) som tillämpas på klasser, som anger att en klass bara ska ha en anledning att ändra. Men i det här fallet handlar det om mikrotjänster, så omfånget blir större än en enda klass. Framför allt måste en mikrotjänst vara självständig, från slutpunkt till slutpunkt, inklusive ansvar för sina egna datakällor.

Extern kontra intern arkitektur och designmönster

Den externa arkitekturen är mikrotjänstarkitekturen som består av flera tjänster, enligt de principer som beskrivs i arkitekturavsnittet i den här guiden. Beroende på vilken typ av mikrotjänst du väljer, och oberoende av mikrotjänstarkitektur på hög nivå, är det dock vanligt och ibland lämpligt att ha olika interna arkitekturer, var och en baserat på olika mönster, för olika mikrotjänster. Mikrotjänsterna kan till och med använda olika tekniker och programmeringsspråk. Bild 6-2 illustrerar denna mångfald.

Diagram comparing external and internal architecture patterns.

Bild 6-2. Extern kontra intern arkitektur och design

I vårt eShopOnContainers-exempel är till exempel katalog-, korg- och användarprofilmikrotjänster enkla (i princip CRUD-undersystem). Därför är deras interna arkitektur och design enkel. Du kan dock ha andra mikrotjänster, till exempel beställningsmikrotjänsten, som är mer komplex och representerar ständigt föränderliga affärsregler med hög grad av domänkomplexitet. I sådana fall kanske du vill implementera mer avancerade mönster inom en viss mikrotjänst, som de som definieras med domändrivna designmetoder (DDD), som vi gör i eShopOnContainers som beställer mikrotjänster. (Vi kommer att granska dessa DDD-mönster i avsnittet senare som förklarar implementeringen av eShopOnContainers som beställer mikrotjänster.)

En annan orsak till en annan teknik per mikrotjänst kan vara varje mikrotjänsts natur. Det kan till exempel vara bättre att använda ett funktionellt programmeringsspråk som F#, eller till och med ett språk som R om du riktar in dig på AI- och maskininlärningsdomäner, i stället för ett mer objektorienterat programmeringsspråk som C#.

Slutsatsen är att varje mikrotjänst kan ha en annan intern arkitektur baserat på olika designmönster. Alla mikrotjänster bör inte implementeras med hjälp av avancerade DDD-mönster, eftersom det skulle vara överkonstruerade. På samma sätt bör komplexa mikrotjänster med ständigt föränderlig affärslogik inte implementeras som CRUD-komponenter, eller så kan du få kod av låg kvalitet.

Den nya världen: flera arkitekturmönster och flerspråkiga mikrotjänster

Det finns många arkitekturmönster som används av programvaruarkitekter och utvecklare. Följande är några (blandning av arkitekturformat och arkitekturmönster):

Du kan också skapa mikrotjänster med många tekniker och språk, till exempel ASP.NET Core Web API:er, NancyFx, ASP.NET Core SignalR (finns med .NET Core 2 eller senare), F#, Node.js, Python, Java, C++, GoLang med mera.

Det viktiga är att inget särskilt arkitekturmönster eller stil, eller någon särskild teknik, är rätt för alla situationer. Bild 6–3 visar vissa metoder och tekniker (men inte i någon särskild ordning) som kan användas i olika mikrotjänster.

Diagram showing 12 complex microservices in a polyglot world architecture.

Bild 6-3. Multi-arkitektoniska mönster och polyglot mikrotjänster världen

Multiarkitekturmönster och flerspråkiga mikrotjänster innebär att du kan blanda och matcha språk och tekniker efter varje mikrotjänsts behov och ändå få dem att prata med varandra. Som du ser i bild 6–3 i program som består av många mikrotjänster (avgränsade kontexter i domändriven designterminologi eller helt enkelt "undersystem" som autonoma mikrotjänster) kan du implementera varje mikrotjänst på ett annat sätt. Var och en kan ha olika arkitekturmönster och använda olika språk och databaser beroende på programmets natur, affärskrav och prioriteringar. I vissa fall kan mikrotjänsterna vara liknande. Men så är vanligtvis inte fallet, eftersom varje undersystems kontextgräns och krav vanligtvis skiljer sig åt.

För ett enkelt CRUD-underhållsprogram kanske det till exempel inte är meningsfullt att utforma och implementera DDD-mönster. Men för kärndomänen eller kärnverksamheten kan du behöva tillämpa mer avancerade mönster för att hantera affärskomplexitet med ständigt föränderliga affärsregler.

Särskilt när du hanterar stora program som består av flera undersystem bör du inte använda en enda arkitektur på den översta nivån baserat på ett enda arkitekturmönster. Till exempel bör CQRS inte användas som en arkitektur på toppnivå för ett helt program, men kan vara användbart för en specifik uppsättning tjänster.

Det finns ingen silverkula eller ett rätt arkitekturmönster för varje enskilt fall. Du kan inte ha "ett arkitekturmönster för att styra dem alla". Beroende på prioriteringarna för varje mikrotjänst måste du välja en annan metod för var och en, enligt beskrivningen i följande avsnitt.