De grenzen van microservices

Hoe ‘Bounded Contexts’ helpen microservices af te bakenen. In een vorige blog heb ik Microservices in een historisch perspectief geplaatst. Eén van de ontwikkelingen die ik daar aan de orde stel, en die heeft bijgedragen aan het mogelijk maken van Microservices, is Domain Driven Design (hierna DDD).

Een belangrijk uitgangspunt van DDD is dat je het systeem moet opdelen in delen waar één taal gesproken wordt, en waarbij die taal goed gedefinieerd moet zijn. Zodra je probeert om een domein af te bakenen waar wezenlijk andere begrippen of betekenissen worden gehanteerd, neemt de complexiteit sterk toe. Begrippen krijgen eigenschappen toebedeeld die eigenlijk net niet passen, waardoor de helderheid verwaterd en uiteindelijk niemand het meer begrijpt … Het is daarmee geen intrinsieke complexiteit meer, maar gemodelleerde complexiteit. De oplossing past niet meer naadloos bij het probleem; niet omdat het probleem zo ingewikkeld is, maar omdat de oplossing meerdere, niet congruente problemen probeert op te lossen. In DDD-terminologie heet zo’n ‘afgebakend taalgebied’ een ‘Bounded Context’.

Een voorbeeld van hoe het fout zou kunnen gaan is het gelijkschakelen van een debiteur aan een klant (en een crediteur aan een leverancier; voor de eenvoud beperk ik me tot debiteur en klant). Voor de volledigheid, de definities:

  • een klant is iemand die iets van je heeft gekocht;
  • een debiteur is iemand van wie je nog geld ontvangt;

Een klant is doorgaans, op enig moment in de tijd, ook debiteur: als je iets koopt, moet je daar ook voor betalen. Er zijn echter tal van omstandigheden waar het niet meer opgaat:

  • zodra de debiteur aan de betalingsverplichting heeft voldaan, is hij geen debiteur meer, terwijl een klant (in principe) voor eeuwig als klant kan worden aangemerkt (ook als iemand drie maanden geleden iets heeft gekocht, is het nog steeds een klant);
  • als een klant, op grond van een klacht een korting heeft bedongen, nadat het aankoopbedrag al is voldaan, is het plots geen debiteur meer maar zelfs een crediteur: hij heeft de korting nog tegoed en nu ben jij de debiteur geworden.

Een dergelijk subtiel taalverschil hoeft niet onmiddellijk te betekenen dat deze twee begrippen niet in hetzelfde domein kunnen leven; het betekent wél dat je niet moet proberen om ze in elkaar te vervlechten. Het zijn wezenlijk verschillende dingen die in je domeinmodel apart gemodelleerd moeten worden. Een klant heeft ander gedrag, en andere eigenschappen dan een debiteur. Als je probeert ze ‘hetzelfde’ te laten zijn, “het is uiteindelijk toch dezelfde persoon”, gaat het in de situaties waar ze verschillen, wringen. Dat wringen is ‘model complexiteit’ die de onderhoudbaarheid en uitbreidbaarheid van het informatiesysteem belemmert. Een model zonder modelcomplexiteit past naadloos op veranderende en onvoorziene situaties. Het beweegt mee met de werkelijkheid, die het ook in de meest extreme omstandigheden toch nog goed weergeeft.

Als beide betekenissen naast elkaar worden gehanteerd kun je doorgaans de een uit de ander afleiden. Bij domein overgangen kun je daarvoor een anti-corruption layer als patroon hanteren. Het is eigenlijk een vertaalmachine, die informatie die wordt ontvangen vertaald naar het eigen domein, en dan pas doorgeeft. Alsof je een Franstalige vrachtbrief automatisch vertaald naar het Nederlands, zodra die door een Nederlandstalige verlader wordt ontvangen …

Daarnaast, en dan komen we bij Microservices, moet het ook een aanleiding zijn om te beoordelen of er wel of niet een domeingrens aangebracht moet worden. Een Microservice moet worden gemodelleerd vanuit één wereldbeeld, vanuit één begrippenkader, vanuit één domein. Daarmee kun je garanderen dat de complexiteit “intrinsieke complexiteit” is, en niet wordt vergroot door begripsverwarring. Door DDD toe te passen als conceptueel uitgangspunt bij de afbakening van je Microservices, borg je de beoogde vereenvoudiging van je applicatielandschap.