Redigera

Dela via


Använda taktisk DDD för att utforma mikrotjänster

Azure Migrate

Domändriven design (DDD) motsätter sig tanken på att ha en enda enhetlig modell för hela systemet. I stället uppmuntrar det till att dela upp systemet i avgränsade kontexter, som var och en har sin egen modell. Under den strategiska fasen av DDD beskriver du företagsdomänen och definierar avgränsade kontexter för dina domänmodeller.

Taktisk DDD är när du definierar domänmodeller med mer precision. De taktiska mönstren används inom en enda, avgränsad kontext. I en mikrotjänstarkitektur, där varje avgränsad kontext är en mikrotjänstkandidat, är vi särskilt intresserade av entitets- och aggregeringsmönstren. Genom att använda dessa mönster kan vi identifiera naturliga gränser för tjänsterna i vårt program (se nästa artikel i den här serien). Som en allmän princip bör en mikrotjänst inte vara mindre än ett aggregat och inte större än en avgränsad kontext. Först ska vi granska de taktiska mönstren. Sedan tillämpar vi dem på den leveransavgränsade kontexten i programmet Drönarleverans.

Översikt över taktiska mönster

Det här avsnittet innehåller en kort sammanfattning av de taktiska DDD-mönstren, så om du redan är bekant med DDD kan du förmodligen hoppa över det här avsnittet. Mönstren beskrivs mer detaljerat i kapitel 5 – 6 i Eric Evans bok och i Implementering av domändriven design av Vaughn Vernon.

Diagram över taktiska mönster i domändriven design

Entiteter. En entitet är ett objekt med en unik identitet som består över tid. I ett bankprogram skulle exempelvis kunder och konton vara entiteter.

  • En entitet har en unik identifierare i systemet, som kan användas för att söka efter eller hämta entiteten. Det betyder inte att identifieraren alltid exponeras direkt för användare. Det kan vara ett GUID eller en primärnyckel i en databas.
  • En identitet kan sträcka sig över flera avgränsade kontexter och kan bestå längre än programmets livslängd. Till exempel är bankkontonummer eller statligt utfärdade ID:er inte knutna till livslängden för ett visst program.
  • Attributen för en entitet kan ändras över tid. Till exempel kan en persons namn eller adress ändras, men de är fortfarande samma person.
  • En entitet kan innehålla referenser till andra entiteter.

Värdeobjekt. Ett värdeobjekt har ingen identitet. Den definieras endast av värdena för dess attribut. Värdeobjekt är också oföränderliga. Om du vill uppdatera ett värdeobjekt skapar du alltid en ny instans som ersätter den gamla. Värdeobjekt kan ha metoder som kapslar in domänlogik, men dessa metoder bör inte ha några sidoeffekter på objektets tillstånd. Vanliga exempel på värdeobjekt är färger, datum, tider och valutavärden.

Aggregeringar. Ett aggregat definierar en konsekvensavgränsning runt en eller flera entiteter. Exakt en entitet i en aggregering är roten. Sökningen görs med rotentitetens identifierare. Andra entiteter i aggregeringen är underordnade roten och refereras genom att följa pekare från roten.

Syftet med ett aggregat är att modellera transaktionella invarianter. Verkliga saker har komplexa relationsnät. Kunder skapar beställningar, beställningar innehåller produkter, produkter har leverantörer och så vidare. Hur garanterar programmet konsekvens om det ändrar flera relaterade objekt? Hur håller vi reda på invarianter och framtvingar dem?

Traditionella program har ofta använt databastransaktioner för att framtvinga konsekvens. I ett distribuerat program är det dock ofta inte möjligt. En enskild affärstransaktion kan omfatta flera datalager, eller vara tidskrävande eller omfatta tjänster från tredje part. I slutändan är det upp till programmet, inte datalagret, att framtvinga de invarianter som krävs för domänen. Det är vad aggregeringar är avsedda att modellera.

Kommentar

En aggregering kan bestå av en enda entitet, utan underordnade entiteter. Det som gör det till en aggregering är transaktionsgränsen.

Domän- och programtjänster. I DDD-termer än en tjänst ett objekt som implementerar viss logik utan att bibehålla något särskilt tillstånd. Evans skiljer mellan domäntjänster, som kapslar in domänlogik, och programtjänster som tillhandahåller tekniska funktioner, till exempel användarautentisering eller att skicka ett SMS. Domäntjänster används ofta till att modellera beteenden som omfattar flera entiteter.

Kommentar

Termen tjänst är överbelastad i programvaruutveckling. Definitionen här är inte direkt relaterad till mikrotjänster.

Domänhändelser. Domänhändelser kan användas till att meddela andra delar av systemet när något händer. Som namnet antyder bör domänhändelser innebära något inom domänen. Till exempel är ”en post har infogats i en tabell” inte en domänhändelse. "En leverans avbröts" är en domänhändelse. Domänhändelser är särskilt relevanta i en arkitektur för mikrotjänster. Eftersom mikrotjänster är distribuerade och inte delar datalager ger domänhändelser ett sätt för mikrotjänster att koordinera med varandra. I artikeln Interservice Communication beskrivs asynkrona meddelanden i detalj.

Det finns några andra DDD-mönster som inte visas här, inklusive fabriker, lagringsplatser och moduler. Dessa kan vara användbara mönster för när du implementerar en mikrotjänst, men de är mindre relevanta när du utformar gränserna mellan mikrotjänster.

Drönarleverans: Tillämpa mönstren

Vi börjar med de scenarier som den fraktbundna kontexten måste hantera.

  • En kund kan begära att en drönare hämtar varor från ett företag som är registrerat hos drönarleveranstjänsten.
  • Avsändaren genererar en tagg (streckkod eller RFID) som ska sättas på paketet.
  • En drönare hämtar och levererar ett paket från källplatsen till målplatsen.
  • När en kund schemalägger en leverans tillhandahåller systemet en ETA baserat på väginformation, väderförhållanden och historiska data.
  • När drönaren är i flygning kan en användare spåra den aktuella platsen och den senaste ETA:n.
  • Tills en drönare har hämtat paketet kan kunden avbryta en leverans.
  • Kunden meddelas när leveransen har slutförts.
  • Avsändaren kan begära leveransbekräftelse från kunden i form av en signatur eller ett fingeravtryck.
  • Användare kan söka efter historiken för en slutförd leverans.

Från dessa scenarier identifierade utvecklingsteamet följande entiteter.

  • Leverans
  • Paket
  • Drönare
  • Konto
  • Bekräftelse
  • Meddelande
  • Tagg

De första fyra, Delivery, Package, Drone och Account, är alla aggregeringar som representerar transaktionskonsekvensgränser. Bekräftelser och Meddelanden är underordnade entiteter för Leveranser, och Taggar är underordnade entiteter för Paket.

Värdeobjekten i den här designen är Plats, ETA, PackageWeight och PackageSize.

För att illustrera, här är ett UML-diagram över leveransaggregatet. Observera att den innehåller referenser till andra aggregeringar, inklusive Konto, Paket och Drone.

UML-diagram över leveransaggregatet

Det finns två domänhändelser:

  • När en drönare är i luften skickar drönarentiteten DroneStatus-händelser som beskriver drönarens plats och status (i luften, landad).

  • Leveransentiteten skickar DeliveryTracking-händelser (Leveransspårning) när leveransstatusen ändras. Dessa omfattar DeliveryCreated (Leverans skapad), DeliveryRescheduled (Leverans ombokad), DeliveryHeadedToDropoff (Leverans på väg till avlämning) och DeliveryCompleted (Leverans slutförd).

Observera att de här händelserna beskriver saker som har en konkret innebörd i domänmodellen. De beskriver något om domänen och är inte bundna till någon viss konstruktion i ett programmeringsspråk.

Utvecklingsteamet identifierade ytterligare ett funktionsområde, som inte passar särskilt bra i någon av de entiteter som beskrivits hittills. Någon del av systemet måste koordinera alla steg som ingår i schemaläggning eller uppdatering av en leverans. Utvecklingsteamet lade därför till två domäntjänster i designen: en Scheduler som samordnar stegen och en övervakare som övervakar statusen för varje steg för att identifiera om några steg har misslyckats eller överskridit tidsgränsen. Det här är en variant av Scheduler Agent Supervisor-mönstret.

Diagram över den reviderade domänmodellen

Nästa steg

Nästa steg är att definiera gränserna för varje mikrotjänst.