DDD eXchange 2017: Good design is imperfect design

26950862354_aa64ad1b5e_k-1.jpg

Eind april trok ik naar Londen voor de tweedaagse DDD eXchange-conferentie. Eric Evans bracht er een inspirerende talk met de aantrekkelijke titel 'Good design is imperfect design'. In deze blogpost deel ik wat ik van hem opstak.

 

Wat is DDD?

Misschien moeten we eerst even toelichten wat DDD precies is. DDD staat voor Domain Driven Design.

Het is een principe dat een taal ontwikkelt tussen de klant en de developer waardoor deze elkaar begrijpen. Hierdoor vermijdt men misvattingen over wat de klant bedoelde en wat de developer implementeert.

 

Ubiquitous language

Deze taal wordt de ubiquitous language genoemd en structureert zich rond het domain model. Dit model is een systeem van abstracties dat verschillende  aspecten van een domein beschrijft en wordt gebruikt om problemen op te lossen die verband houden met dat domein.


Doordat code in afgelijnde contexten (bounded contexts) wordt gebruikt, geeft dit ruimte voor mogelijke aanpassingen in de toekomst.

 

Good design is imperfect design

Eric Evans is de auteur van het boek domain-driven design die voor het eerst de filosofie beschrijft rond het DDD-principe. Dit zijn enkele key-notes die ik heb opgestoken van zijn talk “Good design is imperfect design”.


We moeten stoppen met DDD te gebruiken voor perfectie. Dit leidt namelijk tot eindeloos analyseren en opschonen. Hoewel DDD wel waarde hecht aan opschoning en verfijning.

 

Bounded context

Bounded context zijn bijvoorbeeld een uitdrukkelijke erkenning van de noodzaak om onze modellen te beperken in reikwijdte.  Binnen deze aflijningen gaan we naar verfijning. Deze zorgen dan weer voor mooiere, elegantere modellen. Goede ontwerpen bevatten nog altijd fouten.

Belangrijk is dat er een evenwichtig oogpunt blijft behouden, anders kan dat het project vertragen. Vertragen betekent minder nieuwe ontdekkingen in het domein, minder iteraties en dus een slechter ontwerp.

 

Ball of mud

Het is belangrijk dat heel het team akkoord is met waar de bounded contexts zich bevinden. Respecteer je deze niet dan wordt je code één grote “ball of mud”. De bounded contexts laten toe dat verschillende teams aan hetzelfde project werken. Elk team kan zo in zijn eigen bounded context werken. De anticorruption layer zorgt vervolgens voor de vertalingen tussen de bounded contexts.

 

JodaTime

Geef vreemde namen aan vreemde concepten. Dit legde Eric uit aan de hand van volgend voorbeeld.

In Java is er een library die “JodaTime” heet. Deze library wordt gebruikt om bewerkingen met datums en periodes te doen.

t.plus(p) => t
t= 2017-01-20
p= 1 month
2017-01-20.plus(1 month) => 2017-02-20
2017-01-20.plus(2 month) => 2017-03-20
2017-01-30.plus(1 month) => ?

De laatste regel code zou impliceren dat het als uitkomst 2017-02-30 heeft, maar dat is niet het geval. In plaats daarvan geeft het 2017-02-28 als uitkomst. De naam “plus” die hier gebruikt wordt is eigenlijk niet 100% correct. Dit is geen probleem maar duidt op een imperfectie.

Men zou moeten proberen om enkel eerlijke namen te gebruiken. Het zou beter zijn om voor deze bewerking een vreemde naam te gebruiken. Op deze manier blijft het steeds opvallen, waardoor deze later makkelijker aan te passen is als je er een betere naam voor vindt. Geef je het namelijk vanaf het begin al een nette naam, dan valt deze niet meer op en zal je hier altijd over kijken.

De betere naam voor de “plus”-methode zou zijn: “plus-ish”, “plusRounded” of “laterBy”.


image2.png

Het is belangrijk dat code effectief gebruikt kan worden. Daarom moet je ruimte laten voor imperfectie en creativiteit de loop laten gaan. Dit kan rommelig worden. Maar er is een verschil tussen rommelige code door creativiteit en rommelige code door verwaarlozing. Iteratie na iteratie kom je op steeds mooiere modellen doordat je code opnieuw moet bekijken en je op dat moment misschien betere namen vind voor bepaalde concepten.

Vaak heb je ook modellen die heel net zijn en belangrijke cases heel goed afhandelen, maar niet alle cases. Je hebt dan twee mogelijkheden. Je maakt een model dat minder net is en wel alle cases afhandelt. Of je maakt een net model dat bv 90% van cases afhandelt en de rest in de “spill zone”.

 

Spill zone

De spill zone is één functie/methode die bijvoorbeeld de naam heeft “casesWeHaventFiguredOut”.  Alle cases die op dat moment nog niet in je model passen, los je hier in op door middel van simpele 'if else'-statements, geen verdere abstractie. Dus kom ik dit tegen en dit en dit. Dan doe dat…

Door dit principe toe te passen heb je meer kans om code naar productie te brengen in plaats van dat dit moet uitgesteld worden wegens tijdsgesprek. Het voorkomt abstractie met een slechte naam terwijl de case wellicht nog niet volledig helder was. Na verloop van tijd krijg je misschien nieuwe inzichten waardoor je wel een correct model vindt. Dit in het begin al forceren levert een model op waarbij alles correct in elkaar lijkt te passen, maar eigenlijk rommeltje is. De spill zone is een manier om imperfectie te beheren.

 

Don't repeat yourself

Een belangrijk punt dat Eric Evans aanhaalt is om het DRY (don’t repeat yourself) principe enkel toe te passen binnen een bounded context. Hij illustreert dit met volgende foto:

image1.png

De badkamer en keuken stellen twee verschillende bounded contexts binnen één infrastructuur voor. De afvoer van het toilet en die van de gootsteen zijn gebouwd met vrijwel dezelfde infrastructuur en hebben vrijwel dezelfde doeleinden: afvalstoffen naar de riolering voeren. Dit duidt op duplicatie en geeft zo een hint voor refactoring. Gezond verstand geeft aan dat we dat hier beter niet doen.

Op softwarevlak komen we dit soms ook tegen. Het is niet omdat we bepaalde kenmerken herkennen die hetzelfde zijn, dat we deze ook abstract moeten maken. Het is duidelijk dat we bounded context hier in rekening moeten houden. Als we dit toch abstract zouden maken, zou dit willen zeggen dat beide bounded contexts gebruik maken van dezelfde code. Hebben we een apart team voor beide bounded contexts, dan zijn deze twee teams onherroepelijk aan elkaar verbonden en kunnen ze niet meer apart van elkaar werken. Past namelijk één team iets aan de code aan, dan moet dit verder gecommuniceerd worden naar het andere team. Op deze manier ga je elkaar enkel maar tegen werken.


Wat ik hieruit geleerd heb is: maak bounded contexts, respecteer deze en werk enkel binnenin deze grenzen om conflicten tussen teams en software te vermijden.

Meer weten? Contacteer ons!