Dela via


Utforma en mikrotjänstdomänmodell

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.

Definiera en omfattande domänmodell för varje affärsmikrotjänst eller begränsad kontext.

Målet är att skapa en enda sammanhängande domänmodell för varje affärsmikrotjänst eller begränsad kontext (BC). Tänk dock på att en BC- eller affärsmikrotjänst ibland kan bestå av flera fysiska tjänster som delar en enda domänmodell. Domänmodellen måste avbilda reglerna, beteendet, affärsspråket och begränsningarna för den enda begränsade kontexten eller affärsmikrotjänsten som den representerar.

Mönstret Domänentitet

Entiteter representerar domänobjekt och definieras främst av deras identitet, kontinuitet och beständighet över tid, och inte bara av de attribut som utgör dem. Som Eric Evans säger kallas "ett objekt som främst definieras av dess identitet en entitet". Entiteter är mycket viktiga i domänmodellen, eftersom de är grunden för en modell. Därför bör du identifiera och utforma dem noggrant.

En entitets identitet kan korsa flera mikrotjänster eller begränsade kontexter.

Samma identitet (dvs. samma Id värde, även om det kanske inte är samma domänentitet) kan modelleras över flera avgränsade kontexter eller mikrotjänster. Det innebär dock inte att samma entitet, med samma attribut och logik, skulle implementeras i flera avgränsade kontexter. I stället begränsar entiteter i varje begränsad kontext sina attribut och beteenden till de som krävs i den avgränsade kontextens domän.

Entiteten köparen kan till exempel ha de flesta av en persons attribut som definieras i användarentiteten i profilen eller identitetsmikrotjänsten, inklusive identiteten. Men köparentiteten i den beställande mikrotjänsten kan ha färre attribut, eftersom endast vissa köpardata är relaterade till beställningsprocessen. Kontexten för varje mikrotjänst eller begränsad kontext påverkar dess domänmodell.

Domänentiteter måste implementera beteende förutom att implementera dataattribut.

En domänentitet i DDD måste implementera domänlogik eller beteende som är relaterade till entitetsdata (objektet som används i minnet). Som en del av en orderentitetsklass måste du till exempel ha affärslogik och åtgärder implementerade som metoder för uppgifter som att lägga till ett orderobjekt, dataverifiering och total beräkning. Entitetens metoder tar hand om entitetens invarianter och regler i stället för att ha dessa regler spridda över programskiktet.

Bild 7–8 visar en domänentitet som implementerar inte bara dataattribut utan även åtgärder eller metoder med relaterad domänlogik.

Diagram showing a Domain Entity's pattern.

Bild 7-8. Exempel på en domänentitetsdesign som implementerar data plus beteende

En entitet för en domänmodell implementerar beteenden via metoder, dvs. det är inte en "anemisk" modell. Ibland kan du naturligtvis ha entiteter som inte implementerar någon logik som en del av entitetsklassen. Detta kan inträffa i underordnade entiteter inom en aggregering om den underordnade entiteten inte har någon särskild logik eftersom merparten av logiken definieras i den aggregerade roten. Om du har en komplex mikrotjänst som har logik implementerad i tjänstklasserna i stället för i domänentiteterna kan du hamna i den anemiska domänmodellen, som beskrivs i följande avsnitt.

Omfattande domänmodell jämfört med anemisk domänmodell

I inlägget AnemicDomainModel beskriver Martin Fowler en anemisk domänmodell på det här sättet:

Det grundläggande symptomet på en anemisk domänmodell är att den först rodnar och ser ut som den verkliga saken. Det finns objekt, många namngivna efter substantiven i domänutrymmet, och dessa objekt är anslutna till de omfattande relationer och den struktur som sanna domänmodeller har. Fångsten kommer när du tittar på beteendet, och du inser att det knappast finns något beteende på dessa objekt, vilket gör dem lite mer än påsar med getters och setters.

När du använder en anemisk domänmodell används naturligtvis dessa datamodeller från en uppsättning tjänstobjekt (som traditionellt kallas affärsskiktet) som samlar in all domän- eller affärslogik. Affärslagret ligger ovanpå datamodellen och använder datamodellen precis som data.

Den anemiska domänmodellen är bara en design av processuell stil. Anemiska entitetsobjekt är inte verkliga objekt eftersom de saknar beteende (metoder). De innehåller bara dataegenskaper och är därför inte objektorienterad design. Genom att lägga ut allt beteende i tjänstobjekt (affärsskiktet) får du i princip spaghettikod eller transaktionsskript, och därför förlorar du de fördelar som en domänmodell ger.

Oavsett om din mikrotjänst eller begränsade kontext är mycket enkel (en CRUD-tjänst) kan den anemiska domänmodellen i form av entitetsobjekt med bara dataegenskaper vara tillräckligt bra, och det kanske inte är värt att implementera mer komplexa DDD-mönster. I så fall är det bara en beständighetsmodell eftersom du avsiktligt har skapat en entitet med endast data för CRUD-ändamål.

Därför är mikrotjänstarkitekturer perfekta för en arkitektur med flera arkitekturer beroende på varje begränsad kontext. I eShopOnContainers implementerar till exempel den beställande mikrotjänsten DDD-mönster, men katalogmikrotjänsten, som är en enkel CRUD-tjänst, gör det inte.

Vissa säger att den anemiska domänmodellen är ett antimönster. Det beror verkligen på vad du implementerar. Om mikrotjänsten du skapar är tillräckligt enkel (till exempel en CRUD-tjänst) är det inte ett antimönster att följa den anemiska domänmodellen. Men om du behöver ta itu med komplexiteten i en mikrotjänsts domän som har många ständigt föränderliga affärsregler kan den anemiska domänmodellen vara ett antimönster för den mikrotjänsten eller den avgränsade kontexten. I så fall kan design av den som en omfattande modell med entiteter som innehåller data plus beteende samt implementera ytterligare DDD-mönster (aggregeringar, värdeobjekt osv.) ha stora fördelar för den långsiktiga framgången för en sådan mikrotjänst.

Ytterligare resurser

Mönstret Värdeobjekt

Som Eric Evans har noterat, "Många objekt har inte konceptuell identitet. Dessa objekt beskriver vissa egenskaper hos en sak."

En entitet kräver en identitet, men det finns många objekt i ett system som inte gör det, som mönstret Värdeobjekt. Ett värdeobjekt är ett objekt utan konceptuell identitet som beskriver en domänaspekt. Det här är objekt som du instansierar för att representera designelement som endast berör dig tillfälligt. Du bryr dig om vad de är, inte vilka de är. Exempel är tal och strängar, men kan också vara begrepp på högre nivå, till exempel grupper av attribut.

Något som är en entitet i en mikrotjänst kanske inte är en entitet i en annan mikrotjänst, eftersom den avgränsade kontexten i det andra fallet kan ha en annan betydelse. En adress i ett e-handelsprogram kanske till exempel inte har någon identitet alls, eftersom den kanske bara representerar en grupp attribut för kundens profil för en person eller ett företag. I det här fallet ska adressen klassificeras som ett värdeobjekt. Men i ett program för ett elbolag kan kundadressen vara viktig för företagsdomänen. Därför måste adressen ha en identitet så att faktureringssystemet kan länkas direkt till adressen. I så fall ska en adress klassificeras som en domänentitet.

En person med ett namn och efternamn är vanligtvis en entitet eftersom en person har identitet, även om namnet och efternamnet sammanfaller med en annan uppsättning värden, till exempel om dessa namn också refererar till en annan person.

Värdeobjekt är svåra att hantera i relationsdatabaser och ORM:er som Entity Framework (EF), medan de i dokumentorienterade databaser är enklare att implementera och använda.

EF Core 2.0 och senare versioner innehåller funktionen Ägda entiteter som gör det enklare att hantera värdeobjekt, som vi kommer att se i detalj senare.

Ytterligare resurser

Aggregeringsmönstret

En domänmodell innehåller kluster med olika dataentiteter och processer som kan styra ett betydande funktionsområde, till exempel orderuppfyllelse eller inventering. En mer detaljerad DDD-enhet är aggregeringen, som beskriver ett kluster eller en grupp med entiteter och beteenden som kan behandlas som en sammanhängande enhet.

Du definierar vanligtvis en aggregering baserat på de transaktioner som du behöver. Ett klassiskt exempel är en ordning som också innehåller en lista över orderobjekt. Ett orderobjekt är vanligtvis en entitet. Men det blir en underordnad entitet inom orderaggregatet, som också innehåller orderentiteten som rotentitet, vanligtvis kallad en aggregeringsrot.

Det kan vara svårt att identifiera aggregeringar. En aggregering är en grupp med objekt som måste vara konsekventa tillsammans, men du kan inte bara välja en grupp med objekt och märka dem som en aggregering. Du måste börja med ett domänkoncept och tänka på de entiteter som används i de vanligaste transaktionerna som är relaterade till det konceptet. De entiteter som måste vara transaktionsmässigt konsekventa är de som utgör en aggregering. Att tänka på transaktionsåtgärder är förmodligen det bästa sättet att identifiera aggregeringar.

Mönstret aggregerad rot- eller rotentitet

En aggregering består av minst en entitet: den aggregerade roten, även kallad rotentitet eller primär entitet. Dessutom kan det ha flera underordnade entiteter och värdeobjekt, där alla entiteter och objekt arbetar tillsammans för att implementera det beteende och transaktioner som krävs.

Syftet med en aggregerad rot är att säkerställa aggregatets konsekvens. det bör vara den enda startpunkten för uppdateringar av aggregerade genom metoder eller åtgärder i den aggregerade rotklassen. Du bör göra ändringar i entiteter inom aggregerade endast via den aggregerade roten. Det är aggregatets konsekvensövervakare, med tanke på alla invarianter och konsekvensregler som du kan behöva följa i ditt aggregerat. Om du ändrar en underordnad entitet eller ett värdeobjekt oberoende av varandra kan den aggregerade roten inte se till att aggregeringen är i ett giltigt tillstånd. Det skulle vara som ett bord med ett löst ben. Att upprätthålla konsekvens är huvudsyftet med den aggregerade roten.

I bild 7–9 kan du se exempelaggregat som köparaggregatet, som innehåller en enda entitet (den aggregerade rotköparen). Orderaggregatet innehåller flera entiteter och ett värdeobjekt.

Diagram comparing a buyer aggregate and an order aggregate.

Bild 7-9. Exempel på aggregeringar med flera eller enstaka entiteter

En DDD-domänmodell består av aggregeringar, en aggregering kan bara ha en entitet eller mer och kan även innehålla värdeobjekt. Observera att köparens aggregering kan ha ytterligare underordnade entiteter, beroende på din domän, som i beställningsmikrotjänsten i referensprogrammet eShopOnContainers. Bild 7-9 illustrerar bara ett fall där köparen har en enda entitet, som ett exempel på en aggregering som endast innehåller en aggregerad rot.

För att upprätthålla separationen av aggregeringar och hålla tydliga gränser mellan dem är det en bra idé i en DDD-domänmodell att inte tillåta direkt navigering mellan aggregeringar och endast ha fältet sekundärnyckel (FK), som implementeras i domänmodellen Ordering microservice i eShopOnContainers. Entiteten Order har bara ett sekundärnyckelfält för köparen, men inte en EF Core-navigeringsegenskap, enligt följande kod:

public class Order : Entity, IAggregateRoot
{
    private DateTime _orderDate;
    public Address Address { get; private set; }
    private int? _buyerId; // FK pointing to a different aggregate root
    public OrderStatus OrderStatus { get; private set; }
    private readonly List<OrderItem> _orderItems;
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
    // ... Additional code
}

Att identifiera och arbeta med aggregeringar kräver forskning och erfarenhet. Mer information finns i följande lista över ytterligare resurser.

Ytterligare resurser