Byggande av domänstyrda mikrotjänster

Chandra Ramalingam

Följ

1 juli, 2020 – 18 min read

Bildkrediter:

Tecknet ”micro” i mikrotjänster är visserligen en indikation på tjänstens storlek, men det är inte det enda kriteriet som gör en applikation till en mikrotjänst. När team övergår till en mikrotjänstbaserad arkitektur vill de öka sin smidighet – distribuera funktioner autonomt och ofta. Det är svårt att fastställa en enda kortfattad definition av denna arkitekturstil. Jag gillade den här korta definitionen från Adrian Cockcroft – ”Tjänsteorienterad arkitektur som består av löst kopplade element som har avgränsade sammanhang.”

Tyvärr definierar detta en designheuristik på hög nivå, men Microservices-arkitekturen har några unika egenskaper som skiljer den från den tjänsteorienterade arkitekturen från förr. Nedan följer några av dessa egenskaper. Dessa och några till är väldokumenterade – Martin Fowlers artikel och Sam Newmans Building Microservices, för att nämna några.

  1. Tjänsterna har väldefinierade gränser som är centrerade kring affärskontexten, och inte kring godtyckliga tekniska abstraktioner
  2. Gömmer implementeringsdetaljer och avslöjar funktionalitet genom avsiktsavslöjande gränssnitt
  3. Tjänsterna delar inte sina interna strukturer bortom sina gränser. Till exempel ingen delning av databaser.
  4. Tjänster är motståndskraftiga mot fel.
  5. Team äger sina funktioner självständigt och har förmågan att släppa ändringar självständigt
  6. Team har en kultur av automatisering. Till exempel automatiserad testning, kontinuerlig integration och kontinuerlig leverans

Samt kan vi sammanfatta denna arkitekturstil enligt nedan:

Löst kopplad tjänsteorienterad arkitektur, där varje tjänst är innesluten i en väldefinierad avgränsad kontext, vilket möjliggör snabba, frekventa och tillförlitliga leveranser av applikationer.

Domänstyrd design och avgränsade sammanhang

Kraften hos mikrotjänster kommer från att tydligt definiera deras ansvar och avgränsa dem. Syftet här är att bygga hög sammanhållning inom gränsen och låg koppling utanför den. Det vill säga, saker som tenderar att förändras tillsammans bör höra ihop. Som i många verkliga problem är detta lättare sagt än gjort – företag utvecklas och antaganden ändras. Därför är förmågan till refaktorisering en annan kritisk sak att ta hänsyn till när man utformar system.

Domänstyrd design (DDD) är ett viktigt, och enligt vår åsikt, ett nödvändigt verktyg när man utformar mikrotjänster, oavsett om det gäller att bryta en monolit eller att implementera ett greenfield-projekt. Domänstyrd design, som Eric Evans gjorde känd genom sin bok , är en uppsättning idéer, principer och mönster som hjälper till att utforma mjukvarusystem utifrån den underliggande modellen för affärsområdet. Utvecklare och domänexperter arbetar tillsammans för att skapa affärsmodeller på ett allmängiltigt gemensamt språk. De binder sedan dessa modeller till system där de är meningsfulla och upprättar samarbetsprotokoll mellan dessa system och de grupper som arbetar med dessa tjänster. Ännu viktigare är att de utformar de konceptuella konturerna eller gränserna mellan systemen.

Design av mikrotjänster hämtar inspiration från dessa begrepp eftersom alla dessa principer hjälper till att bygga modulära system som kan förändras och utvecklas oberoende av varandra.

Innan vi går vidare ska vi snabbt gå igenom några av DDD:s grundläggande terminologier. En fullständig översikt över domändriven design ligger utanför ramen för den här bloggen. Vi rekommenderar starkt Eric Evans bok till alla som försöker bygga mikrotjänster

Domän: Representerar vad en organisation gör. I exemplet nedan skulle det vara detaljhandel eller e-handel.

Subdomän: En organisation eller affärsenhet inom en organisation. En domän består av flera underdomäner.

Utomatiskt språk: Detta är det språk som används för att uttrycka modellerna. I exemplet nedan är Item en modell som tillhör Ubiquitous language för var och en av dessa subdomäner. Utvecklare, produktchefer, domänexperter och affärsintressenter kommer överens om samma språk och använder det i sina artefakter – kod, produktdokumentation och så vidare.

Figur 1. Underdomäner och avgränsade sammanhang inom e-handelsområdet

Avgränsade sammanhang: Domänstyrd design definierar Bounded Contexts som ”Den miljö där ett ord eller ett uttalande förekommer som bestämmer dess innebörd”. I korthet innebär detta den gräns inom vilken en modell är meningsfull. I exemplet ovan får ”Item” en annan innebörd i var och en av dessa kontexter. I katalogsammanhanget betyder en artikel en produkt som kan säljas, medan det i varukorgssammanhang betyder den artikel som kunden har lagt till i sin varukorg. I Fulfillment-sammanhang betyder det en lagerartikel som kommer att skickas till kunden. Var och en av dessa modeller är olika, och var och en har en annan innebörd och innehåller eventuellt olika attribut. Genom att separera och isolera dessa modeller inom sina respektive gränser kan vi uttrycka modellerna fritt och utan tvetydighet.

Anmärkning: Det är viktigt att förstå skillnaden mellan Subdomains och Bounded contexts. En subdomän hör hemma i problemutrymmet, det vill säga hur ditt företag ser på problemet, medan Bounded contexts hör hemma i lösningsutrymmet, det vill säga hur vi kommer att genomföra lösningen på problemet. Teoretiskt sett kan varje subdomän ha flera bounded contexts, även om vi strävar efter en bounded context per subdomän.

Hur är Microservices relaterade till Bounded contexts

Nu, var passar Microservices in? Är det rättvist att säga att varje bounded context motsvarar en mikrotjänst? Ja och nej. Vi kommer att se varför. Det kan finnas fall där gränsen eller konturen för din avgränsade kontext är ganska stor.

Figur 2. Begränsad kontext och mikrotjänster

Tänk på exemplet ovan. Den avgränsade kontexten Pricing har tre olika modeller – Price, Priced items och Discounts, som var och en ansvarar för priset på en katalogartikel, beräkning av det totala priset på en lista med artiklar respektive tillämpning av rabatter. Vi skulle kunna skapa ett enda system som omfattar alla ovanstående modeller, men det skulle kunna bli ett orimligt stort program. Varje datamodell har, som tidigare nämnts, sina invarianter och affärsregler. Med tiden, om vi inte är försiktiga, skulle systemet kunna bli en stor lerboll av lera med oklara gränser, överlappande ansvarsområden och förmodligen tillbaka till där vi började – en monolit.

Ett annat sätt att modellera det här systemet är att separera, eller gruppera relaterade modeller i separata mikrotjänster. I DDD kallas dessa modeller – pris, prissatta artiklar och rabatter – för aggregat. Ett aggregat är en fristående modell som består av relaterade modeller. Du kan ändra tillståndet i ett aggregat endast genom ett publicerat gränssnitt, och aggregatet säkerställer konsistens och att invarianterna håller.

Formellt sett är ett aggregat ett kluster av associerade objekt som behandlas som en enhet för dataförändringar. Externa referenser är begränsade till en medlem av AGGREGATE, som betecknas som roten. En uppsättning konsistensregler gäller inom AGGREGATE:s gränser.

Figur 3. Mikrotjänster i prissättningssammanhang

Det är återigen inte nödvändigt att modellera varje aggregat som en separat mikrotjänst. Det visade sig vara så för tjänsterna (aggregaten) i fig 3, men det är inte nödvändigtvis en regel. I vissa fall kan det vara vettigt att hysa flera aggregat i en enda tjänst, särskilt när vi inte helt förstår affärsdomänen. En viktig sak att notera är att konsistens endast kan garanteras inom ett enda aggregat och att aggregaten endast kan ändras genom det offentliggjorda gränssnittet. Varje överträdelse av dessa bär risken att förvandlas till en stor lerkula.

Context maps – A way to carve out accurate microservice boundaries

En annan viktig verktygslåda i din arsenal är konceptet Context maps – återigen från Domain Driven Design. En monolit består vanligtvis av olika modeller, oftast tätt sammankopplade – modellerna känner kanske till varandras intima detaljer, att ändra en kan orsaka sidoeffekter på en annan, och så vidare. När du bryter ner monoliten är det viktigt att identifiera dessa modeller – aggregat i det här fallet – och deras relationer. Kontextkartor hjälper oss att göra just detta. De används för att identifiera och definiera relationer mellan olika avgränsade sammanhang och aggregat. Medan bounded contexts definierar gränsen för en modell – Price, Discounts etc. i exemplet ovan – definierar Context maps relationerna mellan dessa modeller och mellan olika kontexter. Efter att ha identifierat dessa beroenden kan vi bestämma rätt samarbetsmodell mellan de team som ska implementera dessa tjänster.

En fullständig utforskning av Context maps ligger utanför ramen för den här bloggen, men vi illustrerar det med ett exempel. Nedanstående diagram representerar de olika applikationer som hanterar betalningar för en e-handelsorder.

  1. Korgkontexten tar hand om online-auktoriseringar av en beställning; Beställningskontexten bearbetar betalningsprocesser efter fullgörandet, t.ex. avräkningar; Kontaktcentret hanterar eventuella undantag, t.ex. att försöka göra om betalningar och att ändra den betalningsmetod som används för beställningen
  2. För enkelhetens skull antar vi att alla dessa kontexter implementeras som separata tjänster
  3. Alla dessa kontexter kapslar in samma modell.
  4. Bemärk att dessa modeller är logiskt sett desamma. Det vill säga att de alla följer samma Ubiquitous-domänspråk – betalningsmetoder, auktoriseringar och avvecklingar. Bara att de är en del av olika sammanhang.

Ett annat tecken på att samma modell är spridd i olika sammanhang är att alla dessa integreras direkt med en enda betalningsgateway och gör samma operationer som varandra

Fig 4. En felaktigt definierad kontextkarta

Omdefiniera tjänstens gränser – kartlägg aggregaten till rätt kontext

Det finns några problem som är mycket tydliga i ovanstående utformning (fig 4). Betalningsaggregat ingår i flera sammanhang. Det är omöjligt att genomdriva invarianter och konsistens mellan olika tjänster, för att inte tala om samtidighetsproblemen mellan dessa tjänster. Vad händer till exempel om kontaktcentret ändrar den betalningsmetod som är kopplad till beställningen medan tjänsten Orders försöker bokföra avvecklingen av en tidigare inlämnad betalningsmetod. Observera också att varje ändring i betalningsgatewayen skulle tvinga fram ändringar i flera tjänster och potentiellt många team, eftersom olika grupper kan äga dessa kontexter.

Med några justeringar och genom att anpassa aggregaten till rätt kontexter får vi en mycket bättre representation av dessa subdomäner – Fig 5. Det är mycket som har ändrats. Låt oss gå igenom förändringarna:

  1. Betalningsaggregatet har fått ett nytt hem – Betalningstjänst. Denna tjänst abstraherar också Payment gateway från andra tjänster som kräver betaltjänster. Eftersom en enda avgränsad kontext nu äger ett aggregat är invarianterna lätta att hantera; alla transaktioner sker inom samma tjänstegräns, vilket bidrar till att undvika samtidighetsproblem.
  2. Payments aggregate använder ett antikorruptionslager (ACL) för att isolera den centrala domänmodellen från betalningsgatewayens datamodell, som vanligen är en tredjepartsleverantör och som kanske kommer att förändras. Vi kommer att dyka djupare in i applikationsdesignen för en sådan tjänst med hjälp av Ports and Adapters-mönstret i ett framtida inlägg. ACL-skiktet innehåller vanligtvis adaptrar som omvandlar betalningsgatewayens datamodell till Payments aggregerade datamodell.
  3. Vagntjänsten anropar Payments-tjänsten genom direkta API-anrop eftersom vagntjänsten kan behöva slutföra betalningsauktoriseringen medan kunderna befinner sig på webbplatsen
  4. Gör dig en anteckning om interaktionen mellan Orders och Payments-tjänsten. Orders-tjänsten avger en domänhändelse (mer om detta senare i den här bloggen). Betalningstjänsten lyssnar på denna händelse och slutför avvecklingen av ordern
  5. Kontaktcentertjänsten kan ha många aggregat, men vi är bara intresserade av Orders-aggregatet för det här användningsfallet. Denna tjänst sänder ut en händelse när betalningsmetoden ändras, och Payments-tjänsten reagerar på detta genom att reversera det kreditkort som användes tidigare och behandla det nya kreditkortet.

Figur 5. Omdefinierad kontextkarta

En monolitisk eller äldre tillämpning har vanligtvis många aggregat, ofta med överlappande gränser. Att skapa en kontextkarta över dessa aggregat och deras beroenden hjälper oss att förstå konturerna av eventuella nya mikrotjänster som vi kommer att vrida ut ur dessa monoliter. Kom ihåg att framgång eller misslyckande för mikrotjänsternas arkitektur beror på låg koppling mellan aggregaten och hög sammanhållning inom dessa aggregat.

Det är också viktigt att notera att avgränsade kontexter i sig själva är lämpliga sammanhållande enheter. Även om en kontext har flera aggregat kan hela kontexten, tillsammans med dess aggregat, komponeras till en enda mikrotjänst. Vi anser att denna heuristik är särskilt användbar för domäner som är lite obskyra – tänk på en ny affärsverksamhet som organisationen ger sig in på. Du kanske inte har tillräcklig insikt om de rätta separationsgränserna, och en för tidig dekomponering av aggregat kan leda till dyr refaktorisering. Föreställ dig att du måste slå ihop två databaser till en, tillsammans med datamigrering, för att vi råkade upptäcka att två aggregat hör ihop. Men se till att dessa aggregat är tillräckligt isolerade genom gränssnitt så att de inte känner till de intrikata detaljerna hos varandra.

Event Storming – en annan teknik för att identifiera tjänstens gränser

Event Storming är en annan viktig teknik för att identifiera aggregat (och därmed mikrotjänster) i ett system. Det är ett användbart verktyg både för att bryta monoliter och när man utformar ett komplext ekosystem av mikrotjänster. Vi har använt den här tekniken för att bryta ner en av våra komplexa applikationer, och vi har för avsikt att täcka våra erfarenheter av Event Storming i en separat blogg. För omfattningen av den här bloggen vill vi ge en snabb översikt på hög nivå. Titta gärna på Alberto Brandellonis video om ämnet om du är intresserad av att utforska det vidare.

I ett nötskal är Event Storming en brainstormingövning mellan de team som arbetar med en applikation – i vårt fall en monolit – för att identifiera de olika domänhändelserna och processerna som sker i ett system. Teamen identifierar också de aggregat eller modeller som dessa händelser påverkar och eventuella efterföljande effekter av dessa. När grupperna gör denna övning identifierar de olika överlappande begrepp, tvetydigt domänspråk och motstridiga affärsprocesser. De grupperar relaterade modeller, omdefinierar aggregat och identifierar dubbla processer. När de gör framsteg med denna övning blir de avgränsade sammanhang där dessa aggregat hör hemma tydligt. Event Storming-workshops är användbara om alla grupper befinner sig i ett enda rum – fysiskt eller virtuellt – och börjar kartlägga händelser, kommandon och processer på en whiteboard i scrum-stil. I slutet av denna övning finns nedan de vanliga resultaten:

  1. Redovisad lista över aggregat. Dessa blir potentiellt nya mikrotjänster
  2. Domänhändelser som måste flöda mellan dessa mikrotjänster
  3. Kommandon som är direkta anrop från andra program eller användare

Vi har visat ett exempel på en tavla i slutet av en Event Storming-workshop nedan. Det är en bra samarbetsövning för teamen att komma överens om rätt aggregat och avgränsade sammanhang. Förutom att det är en bra teambuildingövning kommer teamen ut från denna session med en gemensam förståelse av domänen, ett allmängiltigt språk och exakta tjänstegränser.

Figur 6. Event Storming board

Kommunikation mellan mikrotjänster

För att snabbt sammanfatta är en monolit värd för flera aggregat inom en enda processgräns. Därför är det möjligt att hantera konsistensen hos aggregaten inom denna gräns. Om en kund till exempel gör en beställning kan vi minska inventarierna av varorna och skicka ett e-postmeddelande till kunden – allt inom en enda transaktion. Alla operationer skulle lyckas eller misslyckas. Men när vi bryter monoliten och sprider aggregaten till olika sammanhang kommer vi att få tiotals eller till och med hundratals mikrotjänster. De processer som hittills funnits inom den enda gränsen för en monolit är nu spridda över flera distribuerade system. Det är mycket svårt att uppnå transaktionsintegritet och konsistens i alla dessa distribuerade system, och det har ett pris – systemens tillgänglighet.

Mikrotjänster är också distribuerade system. Därför gäller CAP-satsen även för dem – ”ett distribuerat system kan bara leverera två av tre önskade egenskaper: konsistens, tillgänglighet och partitionstolerans (C, A och P i CAP)”. I verkliga system är partitionstolerans inte förhandlingsbart – nätverket är opålitligt, virtuella maskiner kan gå ner, latenstiden mellan regioner kan bli värre och så vidare.

Det innebär att vi måste välja antingen tillgänglighet eller konsistens. Nu vet vi att i alla moderna tillämpningar är det inte heller någon bra idé att offra tillgängligheten.

Fig 7. CAP-satsen

Utforma tillämpningar kring eventuell konsistens

Om du försöker bygga transaktioner över flera distribuerade system hamnar du i monolitlandet igen. Fast den här gången blir det den värsta sorten, en distribuerad monolit. Om något av systemen blir otillgängligt blir hela processen otillgänglig, vilket ofta leder till frustrerande kundupplevelser, misslyckade löften och så vidare. Dessutom kan ändringar i en tjänst vanligtvis medföra ändringar i en annan tjänst, vilket leder till komplexa och kostsamma driftsättningar. Därför är det bättre att utforma tillämpningar som skräddarsyr våra användningsfall så att vi kan tolerera lite inkonsekvens till förmån för tillgänglighet. I exemplet ovan kan vi göra alla processer asynkrona och därmed slutligen konsekventa. Vi kan skicka e-post asynkront, oberoende av de andra processerna. Om en utlovad vara inte finns tillgänglig i lagret senare kan varan beställas i efterhand, eller så kan vi sluta ta emot beställningar av varan över ett visst tröskelvärde.
Enstaka gånger kan du stöta på ett scenario som kan kräva starka transaktioner i ACID-stil mellan två aggregat i olika processgränser. Det är ett utmärkt tecken på att se över dessa aggregat och kanske kombinera dem till ett. Event Storming och Context Maps hjälper till att identifiera dessa beroenden i ett tidigt skede innan vi börjar dela upp aggregaten i olika processgränser. Att slå ihop två mikrotjänster till en är kostsamt och det är något vi bör sträva efter att undvika.

Favorisera händelsestyrd arkitektur

Mikrotjänster kan sända ut väsentliga förändringar som sker i deras aggregat. Dessa kallas domänhändelser, och alla tjänster som är intresserade av dessa förändringar kan lyssna på dessa händelser och vidta motsvarande åtgärder inom sina domäner. Denna metod undviker beteendekoppling – en domän föreskriver inte vad de andra domänerna ska göra – och tidsmässig koppling – det framgångsrika slutförandet av en process är inte beroende av att alla system är tillgängliga vid samma tidpunkt. Detta innebär naturligtvis att systemen till slut kommer att vara konsekventa.

Fig 8. Händelsestyrd arkitektur

I exemplet ovan publicerar tjänsten Orders en händelse – Order Cancelled. De andra tjänsterna som har prenumererat på händelsen bearbetar sina respektive domänfunktioner: De andra tjänsterna som har behandlat den aktuella händelsen bearbetar sina respektive funktioner: Betalningstjänsten återbetalar pengarna, inventeringstjänsten justerar inventeringen av varorna osv. Några få saker att notera för att säkerställa tillförlitligheten och motståndskraften hos denna integration:

  1. Producenterna bör se till att de producerar en händelse minst en gång. Om detta misslyckas bör de se till att det finns en reservmekanism för att utlösa händelserna på nytt
  2. Konsumenterna bör se till att de konsumerar händelserna på ett idempotent sätt. Om samma händelse inträffar igen bör det inte finnas någon bieffekt hos konsumenten. Händelser kan också anlända i oordning. Konsumenterna kan använda fälten för tidsstämpel eller versionsnummer för att garantera att händelserna är unika.

Det är kanske inte alltid möjligt att använda händelsebaserad integration på grund av arten av vissa användningsfall. Ta en titt på integrationen mellan kundvagnstjänsten och betalningstjänsten. Det är en synkron integration och har därför några saker som vi bör se upp med. Det är ett exempel på beteendekoppling – kundvagnstjänsten anropar kanske ett REST API från betaltjänsten och instruerar den att godkänna betalningen för en beställning, och tidsmässig koppling – betaltjänsten måste vara tillgänglig för att kundvagnstjänsten ska kunna acceptera en beställning. Denna typ av koppling minskar dessa kontexters självständighet och kan leda till ett oönskat beroende. Det finns några sätt att undvika denna koppling, men med alla dessa alternativ kommer vi att förlora möjligheten att ge omedelbar feedback till kunderna.

  1. Konvertera REST API:et till en händelsebaserad integration. Men detta alternativ kanske inte är tillgängligt om betaltjänsten endast exponerar ett REST API
  2. Karttjänsten accepterar en beställning omedelbart och det finns ett batchjobb som plockar upp beställningarna och anropar betaltjänstens API
  3. Karttjänsten producerar en lokal händelse som sedan anropar betaltjänstens API

En kombination av ovanstående med retries vid fel och otillgänglighet av beroendet i uppströmsledet – betaltjänsten – kan resultera i en mycket mer motståndskraftig design. Till exempel kan den synkrona integrationen mellan varukorgen och betalningstjänsterna backas upp av en händelse- eller batchbaserad omprövning vid fel. Detta tillvägagångssätt har en ytterligare inverkan på kundupplevelsen – kunderna kan ha angett felaktiga betalningsuppgifter, och vi kommer inte att ha dem online när vi behandlar betalningarna offline. Eller så kan det bli en extra kostnad för företaget att återkräva misslyckade betalningar. Men med all sannolikhet överväger fördelarna med att kundvagnstjänsten är motståndskraftig mot att betalningstjänsten inte är tillgänglig eller har fel, mer än bristerna. Vi kan till exempel meddela kunderna om vi inte kan samla in betalningar offline. Kort sagt, det finns kompromisser mellan användarupplevelse, motståndskraft och driftskostnader, och det är klokt att utforma system med dessa kompromisser i åtanke.

Underlätta orkestrering mellan tjänster för konsumentspecifika databehov

En av anti-mönstren i alla tjänsteorienterade arkitekturer är att tjänsterna tillgodoser konsumenternas specifika åtkomstmönster. Vanligtvis sker detta när konsumentgrupperna har ett nära samarbete med tjänstemannagrupperna. Om teamet arbetar med en monolitisk applikation skapar de ofta ett enda API som korsar olika aggregatgränser, vilket leder till att aggregaten kopplas ihop. Låt oss ta ett exempel. Säg att sidan för beställningsuppgifter i webb- och mobilapplikationer behöver visa uppgifter om både en beställning och uppgifter om de återbetalningar som behandlats för beställningen på en enda sida. I en monolitisk applikation frågar ett order GET API – förutsatt att det är ett REST API – efter order och återbetalningar tillsammans, konsoliderar båda aggregaten och skickar ett sammansatt svar till den som ringer. Det är möjligt att göra detta utan större overhead eftersom aggregaten tillhör samma processgräns. Således kan konsumenterna få alla nödvändiga data i ett enda anrop.

Om Orders och Refunds ingår i olika kontexter finns data inte längre inom en enda mikrotjänst eller aggregatgräns. Ett alternativ för att behålla samma funktionalitet för konsumenterna är att göra Order-tjänsten ansvarig för att kalla Refunds-tjänsten och skapa ett sammansatt svar. Detta tillvägagångssätt ger upphov till flera problem:

1. Beställningstjänsten integreras nu med en annan tjänst enbart för att stödja de konsumenter som behöver återbetalningsuppgifter tillsammans med beställningsuppgifter. Beställningstjänsten är mindre självständig nu eftersom alla ändringar i Refunds-aggregatet kommer att leda till en ändring i Order-aggregatet.

2. Beställningstjänsten har en annan integration och därmed en annan felpunkt att ta hänsyn till – om Refunds-tjänsten är nere, kan Beställningstjänsten fortfarande sända partiella data, och kan konsumenterna misslyckas på ett elegant sätt?

3. Om konsumenterna behöver en ändring för att hämta mer data från Refunds-aggregatet, är två team inblandade nu för att göra denna ändring.

4. Detta mönster kan, om det följs över hela plattformen, leda till en invecklad väv av beroenden mellan de olika domäntjänsterna, allt eftersom dessa tjänster tillgodoser anroparnas specifika åtkomstmönster.

Backend for Frontends (BFFs)

Ett tillvägagångssätt för att minska denna risk är att låta konsumentteamen hantera orkestrering mellan de olika domäntjänsterna. När allt kommer omkring känner anroparna bättre till åtkomstmönstren och kan ha fullständig kontroll över eventuella ändringar av dessa mönster. Detta tillvägagångssätt frikopplar domäntjänsterna från presentationsskiktet och låter dem fokusera på de centrala affärsprocesserna. Men om webb- och mobilappar börjar anropa olika tjänster direkt i stället för det enda sammansatta API:et från monoliten, kan det leda till överbelastning av prestanda för dessa appar – flera anrop över nätverk med lägre bandbredd, bearbetning och sammanslagning av data från olika API:er och så vidare.

Istället skulle man kunna använda ett annat mönster som kallas Backend for Front-ends. I det här designmönstret tar en backend-tjänst som skapas och hanteras av konsumenterna – i det här fallet webb- och mobilteamen – hand om integrationen mellan flera domäntjänster enbart för att ge kunderna front-end-upplevelsen. Webb- och mobilteamen kan nu utforma datakontrakten baserat på de användningsfall som de tillgodoser. De kan till och med använda GraphQL i stället för REST API:er för att på ett flexibelt sätt fråga och få tillbaka exakt vad de behöver. Det är viktigt att notera att den här tjänsten ägs och underhålls av konsumentteamen och inte av de team som äger domäntjänsterna. Front-end-teamen kan nu optimera utifrån sina behov – en mobilapp kan begära en mindre nyttolast, minska antalet anrop från mobilappen och så vidare. Ta en titt på den reviderade vyn av orkestreringen nedan. BFF-tjänsten anropar nu både Orders och Refunds domäntjänster för sitt användningsfall.

Fig 9. Backend for Frontends

Det är också användbart att bygga BFF-tjänsten tidigt innan man bryter ner en uppsjö av tjänster från monoliten. Annars måste antingen domäntjänsterna stödja orkestrering mellan domäner, eller så måste webb- och mobilappar anropa flera tjänster direkt från frontend. Båda dessa alternativ kommer att leda till prestandaöverskott, bortkastat arbete och brist på autonomi mellan teamen.

Slutsats

I den här bloggen berörde vi olika begrepp, strategier och designheuristik att ta hänsyn till när vi vågar oss in i mikrotjänsternas värld, närmare bestämt när vi försöker bryta upp en monolit i flera domänbaserade mikrotjänster. Många av dessa är stora ämnen i sig själva, och jag tror inte att vi har gjort tillräckligt rättvisa för att förklara dem i detalj, men vi ville presentera några av de kritiska ämnena och vår erfarenhet av att anta dessa. Ytterligare läsning (länk) avsnittet har några referenser och en del användbart innehåll för alla som vill följa den här vägen.

Uppdatering: Nästa två bloggar i serien är ute. Dessa två bloggar behandlar implementering av microtjänsten Cart, med kodexempel, med hjälp av principerna för domändriven design och designmönster för portar och adaptrar. Huvudfokus för dessa bloggar är att visa hur dessa två principer/mönster hjälper oss att bygga modulära applikationer som är agila, testbara och refaktoriserbara – kort sagt att kunna reagera på den snabba miljö som vi alla verkar i.

Implementing Cart Microservice using Domain Driven Design and Ports and Adapters Pattern – Part 1

Implementing Cart Microservice using Domain Driven Design and Ports and Adapters Pattern – Part 2

Fördjupad läsning

1. Eric Evans’ Domain Driven Design

2. Vaughn Vernons Implementing Domain Driven Design

3. Martin Fowlers artikel om mikrotjänster

4. Sam Newmans Building Microservices

5. Event storming

7. Backend for Frontends

8. Fallacies of distributed computing

Lämna ett svar

Din e-postadress kommer inte publiceras.