Publicerades 11 december 2018

Undvik korrupt data genom strikthet och DDD

Undvik korrupt data genom strikthet och DDD - Mjukvaruarkitektur del 2

Ett systems data blir snabbt korrupt när det saknas tydliga gränser med avseende på, bland annat, affärslogik och modifiering av dess entiteter. Att vara strikt med tillgång till t.ex. databaser tvingas konsumenter av systemet att genomgå den affärsprocess som systemet definierar för att skriva och läsa data. I denna artikel går vi igenom olika tekniker för att åstadkomma detta. Mycket kan härledas till DDD (Domain Driven Design) men kan mycket väl appliceras under andra ramar.

Att data blir korrupt är ett problem i allt för många system. Med korrupt menar jag att egenskaper på entiteter har ogiltiga värden med avseende på det regelverk som gäller. Detta regelverk brukar refereras till som affärsregler eller validering. Affärsregler som definierar vilka värden som är giltiga kommer från verksamhetskrav och är därför av högsta vikt, felaktig data kostar oftast mycket pengar i form av felsökning och försenade affärsprocesser.

I del 1 av denna serie går jag igenom konsten att kunna lita på sina objekt med hjälp av bland annat encapsulation och immutability. I denna del (del 2) är det andra tekniker för att bygga robusta och pålitliga system som gäller.

Skydda datamodellen

Det är allt för vanligt att både personer och andra system har direktaccess till ens databas. När en person eller ett externt system gör åverkan på lagrad data så kan den anses korrupt, eftersom den ändrade eller borttagna informationen inte har genomgått de affärsregler som det ägande systemet har satt upp. Detta medför en överhängande risk att ogiltiga värden slinker in. Dessutom kringgår man spårbarheten på vem som har gjort vad – vilket kan vara av kritisk karaktär.

Det är därför viktigt att datamodellen skyddas genom att begränsa behörigheter till databasen. Endast det tjänstekonto som applikationen drivs av ska ha skrivtillgång och så få personer som möjligt skall ha tillgång att läsa. Inga andra system skall ha en “connection string” till applikationens databas.

Utöver databasbehörigheter bör man även skydda sin datamodell i applikationen. Göm undan modellen så nära databasen som möjligt och översätt datamodellen till din domänmodell/affärsmodell så fort som möjligt. En exponerad datamodell medför risker för att regler kringgås men också risken att andra system blir beroende av datamodellen och därmed försvåras refaktoreringar avsevärt.

Tydliga och strikta beröringspunkter

Vi har redan varit inne på vikten att skydda datamodellen och här går vi djupare in på ämnet. Med beröringspunkter menar jag främst det gränssnitt som externa system och personer använder sig av för att kommunicera med systemet. De modeller som exponeras vid beröringspunkterna kommer externa system ta ett beroende på. När ett beroende på en modell är taget blir det ofantligt mycket svårare att göra förändringar på modellen eftersom det påverkar beroende system.

Läsmodeller

För att förhindra att extern part tar ett beroende på sin datamodell bör ni inte exponera den. Istället bör ni göra er fri från den så tidigt som möjligt genom att konvertera till en modell som kan exponeras. Gör ni detta kan ni refaktorisera datamodellen utan att blanda in övriga parter. Den modell som istället exponeras kan kallas för “läsmodell”. Läsmodellen bör endast innehålla den information som man antingen vill exponera eller som externa parter efterfrågar – inget annat. Gärna så platt som möjligt. En enkel modell är enkel att prata om och resonera kring.

Dessa strikta läsmodeller passar bra både vid integrationer med andra system, men också inom systemets egna gränser. Medvetenheten om vad som exponeras från systemets klasser/lager ökar. Strikta modeller brukar också vara behjälpliga vid enhetstestning eftersom testpunkterna är enklare och man får en bra översikt på vad som bör testas.

CQS – Command Query Separation

Ett bra arkitekturmönster som främjar ovanstående är CQS – Command Query Separation. Detta mönster dikterar en separation av läsningar (query) från skrivningar (commands) vilket medför en rad trevligheter. Ni kan ha helt olika modeller för respektive pipa (läs och skriv) eftersom de är separerade. Lässidan kan vara skräddarsydd och slimmad helt efter konsumentens behov. Detta medför att ni inte längre behöver exponera stora objektgrafer (om så ej efterfrågas såklart). Är ni vana vid OR-Mappers som t.ex. Entity Framework kan ni med fördel ha olika DbContexts för respektive pipa, optimerade för läs kontra skriv. Följer ni CQS får ni ofta fina samt minimala läsmodeller som blir till strikta och tydliga beröringspunkter in till systemet.

Rika domänmodeller

Tydliga ansvarsområden handlar om att förstå den domän som ni arbetar i och att kapsla in logik som hör ihop. Vi kommer nu osökt in på DDD (Domain Driven Design) med rika domänmodeller. Rika domänmodeller är entiteter som kapslar in sitt tillstånd och beteende vilket motsatsen till en anemisk domänmodell där beteendet ligger i domäntjänster och tillståndet i entiteten (POCO-objekt). Fördelen med en rik domänmodell är, eftersom beteendet är inbakat i entiteten, att det blir svårare att kringgå validering och andra processer som entiteten kräver. Markerar man dessutom entitetens tillstånd som “immutable” kan man med största säkerhet garantera att tillståndsförändringar måste ha genomgått den process som definieras av entiteten (eftersom tillståndet endast kan ändras internt i entiteten). I fallet med en anemisk domänmodell kan man inte garantera detta på samma sätt. Dels ligger beteendet i domäntjänster som enkelt kan kringgås (genom att inte använda dem) och dels måste entitetens tillstånd kunna förändras av domäntjänsten vilket gör den till “mutable”.

En entitet har ofta relationer, så kallade navigeringsproperties. Tänk er entiteten Företag som har anställda personer. Dessa personer blir då barnentiteter till Företagets entitet. I föregående stycke skrivs det om vikten av att kapsla in logik som validering och andra processer i sin domänentitet för att säkerställa att dessa har genomgåtts vid förändring av tillstånd. Men hur förhindrar man att en Person inte läggs till som anställd i ett Företag “vid sidan av” och på så sätt kringgår den logik som hör till processen att bli anställd? Det är här aggregatrötter kommer in i bilden.

Aggregatrötter

En aggregatrot är en objektgraf av de domänobjekt som hör ihop och därför bör ses som en enhet. Det är detta domänobjekt som i slutändan lagras i databasen. Aggregatrötterna är de enda domänobjekt som klienter/konsumenter av domänen erhåller referenser till. Det är alltså endast Företag, i exemplet ovan, som exponeras ut från domänlagret och inte dess underobjekt som Person. Det är aggregatroten som definierar alla sätt som den eller sina barnentiteter kan förändras på. Den har metoder för de förändringar som stöds. Därför kan man garantera att en person inte läggs till som anställd på ett företag via någon bakväg. Givetvis skall behörighet till databasen vara högst begränsad så att ingen kan kringgå processen den vägen.

Aggregatroten har olika roll i olika arkitekturer. I ett repository pattern agerar aggregatroten det enda objekt som kan hämtas ur ett repository och den kapslar därför in tillgången till dess barnobjekt. Ni kan därför inte ladda Person-objekt separat. Det är även det objekt som i sin tur lagras i databasen, vilket egentligen är gemensamt mellan alla mönster som använder aggregatrötter. I CQRS finns aggregatroten på Commandsidan (skrivsidan) och är de enda domänobjekt som man interagerar med, precis som i andra mönster.

Konceptet med en aggregatrot är mycket trevligt och hjälper till att hålla systemets data konsistent och korrekt. Två mycket viktiga faktorer för ett lyckat system.

Hedra affärsregler

Avslutningsvis vill jag säga att det är av yttersta vikt att hedra de affärsregler som gäller. Om systemets arkitektur gör det enkelt att hedra affärsreglerna och svårare att kringgå dem är mycket vunnet. Antalet buggar kommer att minska och de som ändå ser dagens ljus blir lättare att hitta.

Andreas Hagsten, Infozone