Från fattiga entiteter till domändriven design

with No Comments

Brukar du bygga applikationer där dina entiteter ser ut ungefär så här?

Det vi ser här är vad Martin Fowler (Patterns of Enterprise Application Architecture) kallar för en “Anemic Domain Model”, vilket är ett sk. Anti-Pattern.
dvs. en domänmodell som har blodbrist, någonting saknas.
Anledningen till att detta är ett antipattern är att den beskriver inte hur modellen ska användas eller vad det innebär att skriva till de olika egenskaper som finns.

t.ex. vad betyder; order.ShippinDate = DateTime.Now;
Betyder det bara att den har fått ett datum för att skeppas?
Eller betyder det att den faktiskt kommer att skeppas nu?
Vilka datum är OK att sätta? kan vi bakåt datera? hur långt?

Hur ser vi på validering?

Vi vet från grundskolan att en triangel alltid har en vinkelsumma på 180 grader, så hur försäkrar vi oss att vår triangelentitet faktiskt är i ett giltigt tillstånd?
Jag har sett åtskilliga hacklösningar där entiteten kanske har en IsValid metod eller liknande.
Men glömmer du att kontrollera denna så kan du ändå råka få in en triangel med vinkelsumman 0 i din databas.
Det för oss in på typsäkerhet, C# är statiskt typat, så det är typsäkert, men ta följande exempel:

Om vi tänker oss vår representation av ett får här.
var sheep = new Sheep();
Det ger oss alltid ett får, eller hur?
Men vad händer om jag kör följande kod:

Representerar det här verkligen ett “får” ?
Jag skulle nog säga att vår data faktiskt representerar en varg, inte ett får, trots att vi är skyddade av typsäkerhet.
En korrekt modell måste ta hänsyn till vilka värden som går att sätta på de olika egenskaperna.
Det för oss in på nästa steg; Primitivtyper

Hur definierar vi vilka värden som är OK att tilldela till Age?
Om vi tar i så kanske ett giltigt värde vore 0-120år, lägg till 10 år till så har vi tagit i ordentligt, 0-130.
Är ålder utanför dessa gränser så är det förmodligen inte korrekt, -5 år är svårt att vara, 2000 år är nog inte heller rimligt.
Namn och Adress, representerar de samma sak? båda är string.
Är det OK att skriva person.Name = SomeAddress;
Inte?, varför? det finns inget i vår modell som talar om att namn och adress representerar olika saker.
Personnummer då, är det med sekelsiffra? med bindestreck?
Allt detta resulterar i en modell som vi vet väldigt lite om, det är omöjligt att läsa koden och utifrån denna förstå vad som avses.
Om vi istället försöker vara lite mer explicita, och få in verksamhetsbegrepp i vår modell;
Vi kan tänka oss att vår triangelentitet skulle kunna se ut på följande sätt:

Nu har vi varit lite mer explicita, det går inte längre tilldela värden till de enskilda egenskaperna utifrån, vi har beskrivit att vinklarna måste sättas tillsammans för att skydda objektets giltighet.
Vi kan aldrig skapa en triangel som har ett felaktigt tillstånd och vi behöver därför inte ha märkliga metoder som “IsValid” eller liknande.
Om vi fortsätter med vår Orderentitet:

Vi har tillfört samma sak här, vår order kommer alltid ha ett korrekt tillstånd, vi kan lätt se vad som avses med de olika metoderna.
Bara genom att läsa så blir det ganska tydligt att order.Ship(someDate) betyder att ordern faktiskt kommer att skeppas vid angivet tillfälle.
Om vi fortsätter med problemet med primitivtyper;
Vi fortsätter med vår Person entitet:

Genom att ersätta våra primitivvärden med egna typer så blir det betydligt tydligare att namn och adress faktiskt inte är samma sak.
Vi kan förhindra att man tilldelar adress till namn osv, dessutom har vi tillfört semantik till varför man kan ändra på vissa saker.
En person kan begära ett namnbyte, eller den kan flytta, allt detta kan vi fånga som begrepp i vår modell.
Vi kan även bygga in begränsningar i dessa typer;

Vi kan så klart göra samma sak gör personnummer, där vi tolkar inkommande sträng till konstruktorn för att avgöra om bindestreck eller sekelsiffra angivits för att sedan hantera detta på ett enhetligt sätt.
Detta gör även så att vi kan skicka runt dessa typer i vårat system, vi kan skicka ett personnummer som CivicId och veta att detta alltid är korrekt eftersom ogiltig data inte accepteras vid skapandet.

Allt detta tillsammans bidrar till en tydligare modell där vi slipper gissa vad som avses i de flesta fall, i optimala fall kan vi även diskutera och visa koden för en verksamhetskunnig.
Det finns så klart massor av mer saker vi kan göra för att få en bättre modell men jag hoppas att detta kan ge inte liten inblick i vad domändriven design innebär.

Follow Roger Alsing:

Developer, Mentor and Architect. Co-Founder of the Akka.NET actor model framework. akka.nethouse.se

Leave a Reply