Een eigenzinnige kijk op Domain Driven Design

Wij bouwen allemaal applicaties. Er zijn vele manieren om tot een succesvol eindresultaat te komen. Ik denk dat het zoeken naar de juiste abstractie in je implementatie vanuit een technisch oogpunt een belangrijke sleutel tot succes is. Als een implementatie te abstract of generiek is, dan wordt de applicatie vaak als complex ervaren. Anderzijds levert een verbose implementatie vaak problemen op in de onderhoudbaarheid. Het vinden van de gewenste balans is één van de moeilijkste onderdelen van softwareontwikkeling. Wellicht helpt Domain Driven Design in het vinden van de juiste abstractie.

Eelco Meuter

 

Domain Driven Design (DDD) is een methodiek om tot een applicatiearchitectuur te komen. Het is voor het eerst beschreven in het boek ‘Domain Driven Design: Tackling complexity in the heart of software’ van Evans (2004).

Het boek is wat academisch, maar bevat vele handige technieken. Helaas gebruikt DDD terminologie die ook gebruikt wordt in andere technieken en principes. Dit werkt enorm verwarrend als je met DDD aan de slag gaat.

Ik raad je dan ook aan je niet blind te staren op de terminologie. Ik zou er vooral pragmatisch mee om te gaan. In deze, wellicht wat eigenzinnige, aanpak leg ik liever de nadruk op de achterliggende concepten. Die aanpak wil ik graag met je delen.

De titel Domain Driven Design suggereert een sterke focus op data. Als je het boek in zijn tijdsgeest plaatst, dan is dit begrijpelijk. Met het woord domain wordt traditioneel het data model aangeduid en het ‘denken vanuit je database ontwerp’ was in die tijd bijna net zo hip als de microservices van vandaag. Volgens mij wordt niet zo zeer het data model an sich bedoeld, maar eerder de achterliggende context van de applicatie. Het duiden van het domein geeft een antwoord op de te verwachte meerwaarde van de applicatie voor de bedrijfsvoering.

Met andere woorden, je benoemt de grenzen van je applicatie en plaatst de applicatie in haar functionele context. Wat is nu wel en, veel belangrijker, wat is nu geen onderdeel van je applicatie. Data is zeker een belangrijk onderdeel, maar processen zijn evenredig belangrijk.

De methodiek van DDD heeft veel aandacht voor het benoemen van elementen uit je ontwerp en de relatie van deze geïdentificeerde elementen met de functionele logica vanuit het perspectief van de eindgebruiker.

Het benoemen van elementen uit je ontwerp en deze direct en eenduidig te koppelen aan functionaliteit is een zeer krachtig concept. Hiermee wordt de focus van het ontwikkelen van een applicatie verplaatst van een technische exercitie naar het ontwikkelen van functionaliteit en de gewenste toegevoegde waarde. Dit maakt DDD met name geschikt voor applicaties waar veel belanghebbenden bij betrokken zijn of waar de problematiek omtrent de applicatie inherent complex is.

Laten we iets nader ingaan op een situatie waar je werkt aan een applicatie met veel belanghebben in een grote organisatie en waarom ik denk dat in zo’n situatie DDD een aanpak kan zijn om tot een architectuur te komen.

In grote organisaties is de afstand tussen eindgebruiker en ontwikkelaar vaak groot. Hiermee loop je het gevaar dat de technische invalshoek de overhand krijgt. Het expliciet duiden van de context rondom de applicatie en de toegevoegde waarde van de te ontwikkelen functionaliteit helpt deze afstand te verkleinen.

Het helpt ook in het verminderen van complexiteit. Dit behoeft wat nadere toelichting. Alle belanghebbenden – eindgebruiker, expert, projectleider, architect, tester, analist, beheerder, ontwikkelaar, directie, nou ja noem maar op – hebben elk een beeld over de applicatie. Dit beeld is identiek in een ideale wereld, maar de praktijk is iets weerbarstiger.

Niet zelden denken de verschillende belanghebbenden dat ze allen dezelfde perceptie over de applicatie hebben, wat leidt tot het niet uitspreken van hun beeld. Immers is het doel van de applicatie duidelijk, toch? Het is dit verschil in inzicht tussen de belanghebbenden en de miscommunicatie dat voor een belangrijk deel bijdraagt aan de complexiteit van de applicatie.

Door met alle belanghebbenden een eenduidige taal te ontwikkelen wordt deze miscommunicatie opgeheven. Hiermee verdwijnt een deel van de ontstane complexiteit. Het doel is hierbij om gesprekspartner van elkaar te worden en zo begrip te verkrijgen voor elkaars uitgangspunten. In DDD termen spreek je dan van een ‘Ubiquitous language’.

Door de verschillende functionaliteiten expliciet te benoemen en direct te koppelen aan elementen uit je ontwerp, wordt duidelijk wat wel en vooral wat niet bij de applicatie of bepaalde functionaliteit hoort. Dit is een zogenaamde ‘Bounded Context’; je bepaalt de grenzen van de applicatie of functionaliteit. Naast een ubiquitous language is een bounded context één van de belangrijkste concepten in DDD.

Het in kaart brengen van deze grenzen wordt een context map genoemd. Om tot een bounded context en context map te komen heb je weinig meer nodig dan een whiteboard, wat te drinken en een luisterend oor. Ik vind het dan vooral belangrijk door te vragen, want het doel van deze sessies is het zo expliciet mogelijk in kaart te brengen van de verschillende functionaliteit, de verwachte toegevoegde waarde, en het bijbehorende kader.

Context maps zijn een handige vorm van documentatie. Zij tonen snel en efficiënt de samenhang tussen de verschillende elementen in je ontwerp. Daarnaast geven ze focus. Het is daarom goed om de resultaten van de whiteboard sessies duidelijk zichtbaar te maken voor het team en onderdeel te maken van technische documentatie en presentaties aan belanghebbenden.

Meerdere contexten zijn heel normaal. Complexe applicaties, zoals microservices, hebben bijvoorbeeld meerdere contexten. Mocht je meerdere contexten identificeren, dan zijn dit goede kandidaten voor modules.

Modules? Ja, modulariteit wordt vaak in relatie gebracht met DDD. Die relatie behoeft wat nuance. Het identificeren van modules wordt gestuurd vanuit de functionaliteit en niet door traditionele architectuurlagen. DDD helpt je bij de identificatie vanwege de directe en eenduidige relatie tussen functionaliteit en techniek. Of je modules nu als autonome applicaties deployed of als java archive publiceert is niet van belang vanuit de context van DDD. Die keuze ligt vooral aan je persoonlijke voorkeur en het applicatielandschap waar je applicatie terecht komt.

Nu de context van je applicatie en de grenzen in kaart gebracht zijn, wordt het tijd om wat concreter te worden. Op de grenzen van een geïdentificeerde bounded context leven de interfaces, ook wel services genoemd, waar de interactie plaatsvindt tussen de applicatie/functionaliteit en haar gebruikers. Vaak hebben deze services meerdere clients.

Nu zijn de woorden service en interfaces wel één van de meest misbruikte woorden in ons vakgebied. Ik gebruik het woord interface voor de methodes die je aanroept. Met een service bedoel ik de class die de interfaces bevat. Een service is dus een contract waar elke interface staat voor een bepaalde afspraak.

Om de buitengrenzen van je applicatie zo efficiënt mogelijk te bewaken, gebruik ik vaak het ports & adapters pattern. De truc bij dit pattern is om de interfaces en services zo specifiek mogelijk te maken, zelfs als een generieke oplossing voor de hand ligt.

In een wereld van vele belanghebbenden of complexe applicaties bestaat generiek namelijk niet. In zo’n wereld prefereert Conway’s law: je bent gebonden aan de manier waarop de organisatie met elkaar communiceert.

Een, voor elke client, specifieke interface respecteert Conway’s wetmatigheid en biedt daarnaast de mogelijkheid de naam van de interface zo te kiezen dat deze kristalhelder is voor beide partijen. Dit is een zogenaamde ‘intention-revealing interface’ die voortkomt uit de ontwikkelde eenduidige taal.

Een service is typisch een ‘single point of entry’ en idealiter stateless. Het is in het geval van het ports & adapters pattern weinig meer dan een façade naar een interne service die het echte werk doet. Het doel van de façade is bovenal het faciliteren van de juiste naam. Verder voorziet de façade in een eventuele validatie, mapping naar een generiek intern datamodel of configuratie voor het juiste protocol.

Voor de interne services die binnen de bounded context leven of voor interacties tussen verschillende bounded contexts, heb je het ports & adapters pattern niet nodig. Hier voldoen de gangbare patterns die gebruikelijk zijn voor het onderliggende framework zoals Spring of Java EE.

Daarnaast spreek je met elke specifieke client een data model af. Ook hier geldt het belang van een eenduidige naamgeving en de data zo specifiek mogelijk te moduleren. Je vraagt of levert middels een interface enkel de data wat nodig is; niet meer en niet minder.

Evans identificeert en categoriseert verschillende typen van objecten. Hoewel hij expliciet benoemt dat deze niet gelimiteerd zijn tot data objecten, worden ze wel vaak als zodanig geïnterpreteerd. Dit komt door de verwarrende naamgeving. Nogmaals, staar je niet blind op die terminologie. Ik zal proberen enkele termen in een paar woorden toe te lichten omdat ze heel vaak gebruikt worden in beschikbare DDD literatuur.

In de basis bestaan er volgens Evans, naast services, value objects en entities. Bij een entity is de traceerbaarheid en lifecycle van belang en bij een value object niet. Value objects zijn daarom immutable. Complexere structuren zijn aggregates die bestaan uit entities en/of value objects. Het zijn objecten, dus ze kunnen zowel data als processen weergeven.

Nu je naast de context van de applicatie ook een beeld hebt van de structuur, wordt het tijd om je te richten op de implementatie in Java code . Hiervoor kan je terugvallen op gebruikelijke principes van OO design. Ik vind het raadzaam om de volgende principes wat meer aandacht te geven. Immers wordt DDD gebruikt voor complexe applicaties.

Het concept van bounded context is evenredig aan encapsulation. Door het concept van bounded context vanaf class, naar package tot applicatie door te voeren krijg je een eenduidig ontwerp. Dit maakt de totale oplossing makkelijker te doorgronden voor alle belanghebbenden.

Zelf vind ik het prettig om de principes van S.O.L.I.D. strikt te hanteren. S.O.L.I.D. staat voor Single responsibility, Open-closed, Liskov substitution, Interface segregation, Dependency inversion. Het zijn vijf basisprincipes in OO design; je gebruikt ze waarschijnlijk dagelijks al zonder dat je het realiseert.  Mocht je hier nog niet van gehoord hebben, dan raad ik je zeker aan hier in te duiken. Het toepassen van de S.O.L.I.D. principes leidt tot hele schone code waar de intentie vaak meteen duidelijk is.

Ik hoop dat ik je met dit artikel heb kunnen interesseren in de technieken van DDD. Het is een interessant en omvattend onderwerp. Mocht je meer willen weten, dan is het boek een goed startpunt. Je kan natuurlijk ook altijd contact opnemen.