Vroeger was het simpel: je hele applicatie draaide in één proces. Eén codebase, één deployment, alles in hetzelfde geheugen. De monoliet. Niet sexy, wel overzichtelijk.
Microservices beloofden dat beter te doen. Losse services. Eigen deployments. API-gateways. Kubernetes. Service mesh. Alles netjes opgesplitst. Teams autonoom.
Totdat je naar runtime-gedrag kijkt.
Dan blijkt een simpele gebruikersactie afhankelijk van veertien services, drie databases, twee queues, een externe identity provider en een caching layer die stilletjes het verschil maakt tussen 200ms en 12 seconden.
De monoliet is niet verdwenen. Hij draait alleen nu verspreid over het netwerk. Het maakt de coupling alleen moeilijker zichtbaar.
De illusie van onafhankelijkheid
Microservices worden verkocht als onafhankelijk deploybare componenten. In theorie klopt dat. En ja — veel teams gebruiken message brokers en event-driven patronen om services te ontkoppelen. Dat helpt.
Maar kijk naar het pad dat de gebruiker raakt: de API-call die een antwoord verwacht. Daar is async zelden een optie. Async messaging vermindert runtime coupling, maar verwijdert business coupling niet. Een gebruiker die een order plaatst, wil een bevestiging — geen “we laten het je later weten.”
En precies op die paden zie ik bij vrijwel elk project hetzelfde ontstaan: Service A wacht synchroon op B voor autorisatie. B checkt bij C voor pricing. C haalt voorraad op bij D. Ondertussen wacht de gebruiker nog steeds.
Op papier zijn dat vier losse services. In runtime is het één ketting.
En kettingen hebben een vervelende eigenschap: ze zijn zo sterk als de zwakste schakel.
Een netwerkcall is geen functiecall
Dit verschil wordt structureel onderschat.
Een functiecall binnen een monoliet is voorspelbaar: geheugen lokaal, latency microseconden, nauwelijks externe factoren. Een netwerkcall introduceert een compleet ander faalmodel: latency, timeouts, retries, DNS-issues, TLS-handshakes, connection pooling, transient failures.
Wat lokaal milliseconden kostte, wordt onder belasting ineens thread exhaustion en cascading failures. Niet omdat de code slecht is, maar omdat het netwerk zich niet gedraagt als geheugen — en je architectuur doet alsof het dat wel doet.
En het verraderlijke: die degradatie verloopt zelden lineair. Hij veroorzaakt wachtrijen, retries, resource contention en backpressure in elk systeem dat van hem afhankelijk is. De wiskunde erachter is simpel maar onverbiddelijk — uit queueing theory weten we dat wachttijden exponentieel groeien met de belasting. Een service op 50% bezetting heeft een wachttijd gelijk aan de verwerkingstijd. Op 90% is die negen keer zo lang. Op 95% negentien keer.
Dat betekent dat een service die 300ms trager wordt, niet 300ms extra latency veroorzaakt. Hij duwt zichzelf en alles wat van hem afhankelijk is voorbij een kantelpunt. Wachtrijen lopen vol, retries stapelen op, threads raken uitgeput. Eén trage schakel kan tien gezonde services meesleuren. En de werkelijke bron is bijna nooit de plek waar het alarm afgaat.
Dat is precies waar de Fallacies of Distributed Computing over gaan. En toch zie ik ze elke keer opnieuw terugkomen, verpakt in een strakke architectuurplaat.
Hoe een distributed monolith ontstaat
Het ontstaat zelden door slechte engineers. Het ontstaat doordat teams functionaliteit opsplitsen langs logische lijnen — users, orders, inventory, payments, notifications — terwijl de onderliggende businessprocessen sterk gekoppeld blijven.
Een order plaatsen vereist nog steeds: authenticatie, voorraadcontrole, prijsberekening, betaling, en notificatie. Alleen lopen die afhankelijkheden nu over HTTP of gRPC in plaats van in-process.
De koppeling verdwijnt niet. Hij wordt moeilijker zichtbaar, lastiger debugbaar, en gevoeliger voor latency.
Observability is geen luxe meer
In een monoliet kun je nog lineair debuggen. Een stack trace vertelt je redelijk waar het misgaat. Bij microservices bestaat die luxe niet.
Een foutmelding in Service A kan veroorzaakt worden door een timeout in C, trage storage in D, backpressure in een queue, of een dependency die “half stuk” is — net genoeg werkend om geen alarm af te laten gaan, maar te traag om het systeem gezond te houden.
Distributed tracing, correlation IDs en structured logging zijn hierin niet optioneel. Ze zijn het verschil tussen “we snappen ons eigen systeem” en “we deployen en hopen dat het goed gaat.”
Meer services ≠betere architectuur
Microservices lossen echte problemen op: team-autonomie, onafhankelijke deployments, gerichte schaalbaarheid, fault isolation.
Maar ze introduceren minstens evenveel complexiteit: netwerkgedrag, versiebeheer, deployment-coördinatie, operationele afhankelijkheden, en runtime-instabiliteit.
De afweging wordt te weinig expliciet gemaakt. De vraag zou niet moeten zijn “hoe splitsen we de applicatie op?”, maar “rechtvaardigt onze schaal en teamgrootte deze operationele complexiteit?” Voor veel organisaties is het eerlijke antwoord: nee.
Wat je eraan doet
Het herkennen van een distributed monolith is stap één. Maar herkennen zonder handelen levert alleen cynisme op. Een paar patronen die ik in de praktijk effectief zie:
Maak afhankelijkheden asynchroon waar dat kan. Niet elk verzoek hoeft synchroon afgehandeld te worden. Event-driven patronen met een message broker ontkoppelen services in tijd — en daarmee in beschikbaarheid. Een order hoeft niet te wachten tot de notificatie verstuurd is.
Ontwerp voor failure, niet alleen voor success. Circuit breakers, bulkheads, timeouts en fallbacks zijn geen optimalisaties. Ze zijn basisarchitectuur zodra je over het netwerk communiceert.
Durf boundaries te hertekenen. Als twee services altijd samen moeten deployen, altijd samen falen, en altijd synchroon op elkaar wachten — dan zijn het geen twee services. Dan is het één service met een netwerkcall ertussen. Trek de consequentie en merge ze.
Investeer in observability vóórdat je het nodig hebt. Distributed tracing en structured logging achteraf toevoegen aan een landschap van dertig services is een nachtmerrie. Begin ermee bij service twee.
Het echte onderscheid
Goede distributed architectuur onderscheidt zich niet wanneer alles werkt. Het onderscheidt zich wanneer delen van het systeem dat niet meer doen.
De vraag is niet of er iets faalt. Bij voldoende schaal faalt er altijd iets. De vraag is of je systeem ontworpen is om daar mee om te gaan — of dat één trage database het hele landschap plattrekt.
Dat is het verschil tussen microservices als architectuurkeuze en microservices als hype. En het eerlijke gesprek daarover begint bij het durven kijken naar runtime-gedrag in plaats van architectuurdiagrammen.