De afgelopen tien jaar leek de boodschap helder: monolith bad, microservices good. Maar steeds meer teams ontdekken dat microservices niet de zilveren kogel zijn die ze beloofd werden. De operationele complexiteit, de netwerk-latency, de gedistribueerde debugging - het is een hoge prijs voor ontkoppeling die je misschien niet eens nodig had. Er is een middenweg: de modulaire monoliet, of zoals wij het noemen, de modulith.

Het probleem met de klassieke monoliet

Laten we eerlijk zijn: de traditionele monoliet verdient zijn slechte reputatie deels. Code die na jaren groei een big ball of mud is geworden - waar elke wijziging onvoorspelbare gevolgen heeft, waar geen duidelijke grenzen bestaan, en waar nieuwe ontwikkelaars weken nodig hebben om productief te worden. Dat willen we niet.

Maar het probleem is niet dat de code in één deployment zit. Het probleem is het gebrek aan structuur. En dat los je niet op door je code over twintig repositories en vijftig containers te verspreiden.

Wat is een modulith?

Een modulaire monoliet is een applicatie die als één eenheid wordt gedeployed, maar intern is opgebouwd uit duidelijk afgebakende modules. Elke module:

  • Heeft een eigen domeinmodel en eigen dataopslag (of schema)
  • Communiceert met andere modules via gedefinieerde interfaces - niet via directe databasequeries
  • Kan onafhankelijk ontwikkeld en getest worden
  • Heeft expliciete afhankelijkheden - geen impliciete koppelingen via gedeelde tabellen

Het verschil met microservices: de communicatie is in-process in plaats van over het netwerk. Geen HTTP-calls tussen modules, geen message broker nodig voor eenvoudige interacties, geen distributed transactions.

Wanneer is een modulith de betere keuze?

Een modulaire monoliet is vaak de juiste keuze wanneer:

  • Je hebt geen onafhankelijke schaalbaarheid nodig per component
  • Je wilt snelle communicatie tussen modules zonder netwerk-overhead
  • Je wilt transactionele consistentie waar dat nodig is, zonder Saga-patronen
  • Je wilt eenvoudige deployment en debugging - één process, één log, één debugger

De architectuur in de praktijk

In .NET bouwen we een modulith typisch als volgt:

  • Elke module is een apart project (class library) met een publieke API (interfaces en DTOs)
  • Modules registreren zich via dependency injection - de host-applicatie weet welke modules er zijn
  • Inter-module communicatie verloopt via een in-process mediator of via gedefinieerde interfaces
  • Elke module beheert zijn eigen database-schema of zelfs een eigen database
  • Domein-events worden in-process gepubliceerd en afgehandeld

Het cruciale principe: modules mogen elkaars interne implementatie niet kennen. Ze communiceren via publieke contracten, precies zoals microservices dat doen - maar zonder de netwerk-grens.

De weg naar microservices houden we open

Het mooie van een goed gestructureerde modulith is dat de extractie naar een microservice relatief eenvoudig is. Omdat modules al communiceren via gedefinieerde interfaces, hoef je alleen de communicatielaag te vervangen: van in-process naar HTTP of messaging. De domeinlogica en het datamodel veranderen niet.

Dit is het pad dat we bij veel klanten adviseren: begin als modulith, meet waar de bottlenecks zitten, en extraheer alleen die modules die echt onafhankelijk moeten schalen of deployen.

Valkuilen

Een modulith vereist discipline. Zonder strenge handhaving van module-grenzen verval je snel terug in een gewone monoliet. Praktische tips:

  • Gebruik NetArchTest of ArchUnitNET om afhankelijkheden te valideren in je build
  • Maak module-grenzen zichtbaar - aparte solution folders, eigen namespaces
  • Review elke cross-module dependency expliciet
  • Behandel de publieke API van een module als een contract - breaking changes vereisen overleg

De modulaire monoliet is geen compromis. Het is een bewuste architectuurkeuze die complexiteit beheersbaar houdt zonder de operationele last van een gedistribueerd systeem. Bij NForza bouwen we al jaren systemen op deze manier - pragmatisch, schaalbaar, en klaar voor de toekomst.