Dela via


Utforma en DDD-orienterad mikrotjänst

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.

Domändriven design (DDD) förespråkar modellering baserat på verkligheten i verksamheten som relevant för dina användningsfall. När det gäller att skapa program talar DDD om problem som domäner. Den beskriver oberoende problemområden som avgränsade kontexter (varje avgränsad kontext korrelerar med en mikrotjänst) och betonar ett gemensamt språk för att prata om dessa problem. Det föreslår också många tekniska begrepp och mönster, till exempel domänentiteter med omfattande modeller (ingen anemisk domänmodell), värdeobjekt, aggregeringar och aggregerade rotregler (eller rotentitet) som stöd för den interna implementeringen. I det här avsnittet beskrivs design och implementering av de interna mönstren.

Ibland uppfattas dessa tekniska DDD-regler och mönster som hinder som har en brant inlärningskurva för att implementera DDD-metoder. Men den viktiga delen är inte själva mönstren, utan att organisera koden så att den är anpassad till affärsproblemen och använder samma affärsvillkor (allestädes närvarande språk). Dessutom bör DDD-metoder endast tillämpas om du implementerar komplexa mikrotjänster med betydande affärsregler. Enklare ansvarsområden, till exempel en CRUD-tjänst, kan hanteras med enklare metoder.

Var gränserna ska ritas är den viktigaste uppgiften när du utformar och definierar en mikrotjänst. DDD-mönster hjälper dig att förstå komplexiteten i domänen. För domänmodellen för varje begränsad kontext identifierar och definierar du de entiteter, värdeobjekt och aggregeringar som modellerar din domän. Du skapar och förfinar en domänmodell som finns inom en gräns som definierar din kontext. Och det är explicit i form av en mikrotjänst. Komponenterna inom dessa gränser blir dina mikrotjänster, men i vissa fall kan en BC- eller affärsmikrotjänst bestå av flera fysiska tjänster. DDD handlar om gränser och det är även mikrotjänster.

Håll gränserna för mikrotjänstkontexten relativt små

Att avgöra var gränserna ska placeras mellan avgränsade kontexter balanserar två konkurrerande mål. Först vill du först skapa minsta möjliga mikrotjänster, även om det inte bör vara huvuddrivrutinen. du bör skapa en gräns för saker som behöver sammanhållning. För det andra vill du undvika pratsam kommunikation mellan mikrotjänster. Dessa mål kan motsäga varandra. Du bör balansera dem genom att dela upp systemet i så många små mikrotjänster som möjligt tills kommunikationsgränserna växer snabbt med varje ytterligare försök att separera en ny begränsad kontext. Sammanhållning är nyckeln inom en enda begränsad kontext.

Det liknar den olämpliga intimitetskoddoften när du implementerar klasser. Om två mikrotjänster behöver samarbeta mycket med varandra bör de förmodligen vara samma mikrotjänst.

Ett annat sätt att se på denna aspekt är autonomi. Om en mikrotjänst måste förlita sig på en annan tjänst för att direkt betjäna en begäran är den inte helt autonom.

Lager i DDD-mikrotjänster

De flesta företagsprogram med betydande affärs- och teknisk komplexitet definieras av flera lager. Lagren är en logisk artefakt och är inte relaterade till distributionen av tjänsten. De finns för att hjälpa utvecklare att hantera komplexiteten i koden. Olika lager (t.ex. domänmodelllagret jämfört med presentationslagret osv.) kan ha olika typer, vilket kräver översättningar mellan dessa typer.

En entitet kan till exempel läsas in från databasen. Sedan kan en del av den informationen, eller en aggregering av information, inklusive ytterligare data från andra entiteter, skickas till klientgränssnittet via ett REST-webb-API. Poängen här är att domänentiteten finns i domänmodelllagret och inte bör spridas till andra områden som den inte tillhör, till exempel presentationsskiktet.

Dessutom måste du ha alltid giltiga entiteter (se avsnittet Designa valideringar i domänmodellskiktet ) som styrs av aggregerade rötter (rotentiteter). Därför bör entiteter inte vara bundna till klientvyer, eftersom vissa data fortfarande inte kan verifieras på användargränssnittsnivå. Den här anledningen är vad ViewModel är till för. ViewModel är en datamodell uteslutande för presentationslagerbehov. Domänentiteterna hör inte direkt till ViewModel. I stället måste du översätta mellan ViewModels och domänentiteter och vice versa.

När du hanterar komplexiteten är det viktigt att ha en domänmodell som styrs av aggregerade rötter som ser till att alla invarianter och regler som är relaterade till den gruppen av entiteter (aggregering) utförs via en enda startpunkt eller grind, den aggregerade roten.

Bild 7–5 visar hur en skiktad design implementeras i eShopOnContainers-programmet.

Diagram showing the layers in a domain-driven design microservice.

Bild 7-5. DDD-lager i beställningsmikrotjänsten i eShopOnContainers

De tre lagren i en DDD-mikrotjänst som Ordering. Varje lager är ett VS-projekt: Programskiktet är Ordering.API, Domänskiktet är Ordering.Domain och infrastrukturskiktet är Ordering.Infrastructure. Du vill utforma systemet så att varje lager endast kommunicerar med vissa andra lager. Den metoden kan vara enklare att tillämpa om skikt implementeras som olika klassbibliotek, eftersom du tydligt kan identifiera vilka beroenden som anges mellan bibliotek. Domänmodelllagret bör till exempel inte vara beroende av något annat lager (domänmodellklasserna ska vara oformaterade gamla klassobjekt eller POCO-klasser). Som visas i bild 7-6 har biblioteket Ordering.Domain layer endast beroenden för .NET-biblioteken eller NuGet-paketen, men inte på något annat anpassat bibliotek, till exempel databibliotek eller beständighetsbibliotek.

Screenshot of Ordering.Domain dependencies.

Bild 7-6. Lager som implementeras som bibliotek ger bättre kontroll över beroenden mellan lager

Domänmodelllagret

Eric Evans utmärkta bok Domain Driven Design säger följande om domänmodelllagret och programskiktet.

Domänmodelllager: Ansvarig för att representera begrepp i verksamheten, information om affärssituationen och affärsregler. Tillstånd som återspeglar affärssituationen styrs och används här, även om den tekniska informationen om lagringen delegeras till infrastrukturen. Det här lagret är hjärtat i affärsprogramvaran.

Domänmodelllagret är där verksamheten uttrycks. När du implementerar ett mikrotjänstdomänmodelllager i .NET kodas det lagret som ett klassbibliotek med de domänentiteter som samlar in data plus beteende (metoder med logik).

Efter principerna persistence ignorance och infrastructure ignorance måste det här lagret helt ignorera information om datapersistence. Dessa beständighetsuppgifter bör utföras av infrastrukturlagret. Därför bör det här lagret inte ha direkta beroenden för infrastrukturen, vilket innebär att en viktig regel är att domänmodellens entitetsklasser ska vara POCO:er.

Domänentiteter bör inte ha något direkt beroende (som härleds från en basklass) för något ramverk för dataåtkomstinfrastruktur som Entity Framework eller NHibernate. Helst bör dina domänentiteter inte härledas från eller implementera någon typ som definierats i något infrastrukturramverk.

De flesta moderna ORM-ramverk som Entity Framework Core tillåter den här metoden, så att dina domänmodellklasser inte är kopplade till infrastrukturen. Att ha POCO-entiteter är dock inte alltid möjligt när du använder vissa NoSQL-databaser och ramverk, till exempel Actors och Reliable Collections i Azure Service Fabric.

Även om det är viktigt att följa principen Om beständighet för din domänmodell bör du inte ignorera beständighetsproblem. Det är fortfarande viktigt att förstå den fysiska datamodellen och hur den mappar till entitetsobjektmodellen. Annars kan du skapa omöjliga design.

Den här aspekten innebär inte heller att du kan ta en modell som är utformad för en relationsdatabas och flytta den direkt till en NoSQL- eller dokumentorienterad databas. I vissa entitetsmodeller kan modellen passa, men vanligtvis inte. Det finns fortfarande begränsningar som din entitetsmodell måste följa, baserat både på lagringstekniken och ORM-tekniken.

Programlagret

När vi går vidare till programskiktet kan vi återigen citera Eric Evans bok Domain Driven Design:

Programlager: Definierar de jobb som programvaran ska utföra och dirigerar uttrycksfulla domänobjekt för att lösa problem. De uppgifter som det här lagret ansvarar för är meningsfulla för verksamheten eller nödvändiga för interaktion med programskikten i andra system. Det här lagret hålls tunt. Den innehåller inte affärsregler eller kunskap, utan samordnar bara uppgifter och ombud arbetar med samarbeten mellan domänobjekt i nästa lager nedåt. Det har inte tillstånd som återspeglar affärssituationen, men det kan ha tillstånd som återspeglar förloppet för en uppgift för användaren eller programmet.

En mikrotjänsts programlager i .NET kodas ofta som ett ASP.NET Core Web API-projekt. Projektet implementerar mikrotjänstens interaktion, fjärrnätverksåtkomst och de externa webb-API:er som används från användargränssnittet eller klientapparna. Den innehåller frågor om du använder en CQRS-metod, kommandon som godkänts av mikrotjänsten och till och med händelsedriven kommunikation mellan mikrotjänster (integrationshändelser). Det ASP.NET Core Web API som representerar programskiktet får inte innehålla affärsregler eller domänkunskaper (särskilt domänregler för transaktioner eller uppdateringar); dessa ska ägas av klassbiblioteket för domänmodellen. Programlagret får endast samordna uppgifter och får inte innehålla eller definiera något domäntillstånd (domänmodell). Den delegerar körningen av affärsregler till själva domänmodellklasserna (aggregerade rötter och domänentiteter), som i slutändan uppdaterar data inom dessa domänentiteter.

I grund och botten är programlogik där du implementerar alla användningsfall som är beroende av en viss klientdel. Till exempel implementeringen som är relaterad till en webb-API-tjänst.

Målet är att domänlogik i domänmodelllagret, dess invarianter, datamodellen och relaterade affärsregler måste vara helt oberoende av presentations- och programskikten. Mest av allt får domänmodelllagret inte vara direkt beroende av något infrastrukturramverk.

Infrastrukturlagret

Infrastrukturlagret är hur de data som ursprungligen lagras i domänentiteter (i minnet) bevaras i databaser eller i ett annat beständigt arkiv. Ett exempel är att använda Entity Framework Core-kod för att implementera de databasmönsterklasser som använder en DBContext för att spara data i en relationsdatabas.

I enlighet med tidigare nämnda principer för beständighets- och infrastrukturs okunnighet får infrastrukturlagret inte "förorena" domänmodelllagret. Du måste hålla domänmodellens entitetsklasser agnostiska från infrastrukturen som du använder för att bevara data (EF eller något annat ramverk) genom att inte ta hårda beroenden på ramverk. Ditt lagerklassbibliotek för domänmodell bör bara ha din domänkod, bara POCO-entitetsklasser som implementerar hjärtat i din programvara och som är helt frikopplade från infrastrukturtekniker.

Därför bör dina lager eller klassbibliotek och projekt i slutändan vara beroende av ditt domänmodellskikt (bibliotek), inte tvärtom, som visas i bild 7–7.

Diagram showing dependencies that exist between DDD service layers.

Bild 7-7. Beroenden mellan lager i DDD

Beroenden i en DDD-tjänst, programskiktet beror på domän och infrastruktur och infrastruktur är beroende av domän, men domänen är inte beroende av något lager. Den här lagerdesignen bör vara oberoende för varje mikrotjänst. Som tidigare nämnts kan du implementera de mest komplexa mikrotjänster som följer DDD-mönster, samtidigt som du implementerar enklare datadrivna mikrotjänster (enkel CRUD i ett enda lager) på ett enklare sätt.

Ytterligare resurser