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

Azure Migrate

Under den strategiska fasen av domändriven design (DDD) mappar du ut affärsdomä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 arkitektur för mikrotjänster är vi särskilt intresserade av entitets- och aggregerade mönster. 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 gå igenom de taktiska mönstren. Sedan tillämpar vi dem på kontexten Leveransavgränsad i drone delivery-programmet.

Översikt över de taktiska mönstren

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 Domain-Driven 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:t inte knutna till livslängden för ett visst program.
  • Attributen för en entitet kan ändras med tiden. En persons namn eller adress kan till exempel ä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. Alla 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å invarianterna och ser till att de bibehålls?

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 sträcka sig över 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.

Anteckning

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.

Anteckning

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) för 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å ruttinformation, 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 är klar.
  • Avsändaren kan begära leveransbekräftelse från kunden, i form av en signatur eller ett fingertryck.
  • 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, Leverans, Paket, Drönare och Konto, ä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 Location, ETA, PackageWeight och PackageSize.

Här är ett UML-diagram över leveransaggregatet. Observera att den innehåller referenser till andra aggregat, 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 har därför lagt 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övervakarens mönster.

Diagram över den reviderade domänmodellen

Nästa steg

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