Event-driven architectuur voor SaaS.
Wanneer queues en events beter zijn dan synchrone API-calls. Over decoupling, schaalbaarheid en het bouwen van systemen die niet vastlopen.
Synchrone requests: het probleem
Een gebruiker klikt op "Verstuur factuur". Achter de schermen gebeurt het volgende: er wordt een PDF gegenereerd, die PDF wordt opgeslagen, er gaat een e-mail naar de klant, de factuur wordt gesynchroniseerd met het boekhoudpakket en er wordt een webhook verstuurd naar een extern systeem. Dat alles in een enkel HTTP-request. De gebruiker staart naar een ladend scherm en hoopt dat het lukt.
Dit is het probleem met synchrone architectuur. Elke stap in de keten moet slagen voordat de volgende begint, en de gebruiker wacht tot alles klaar is. Duurt de PDF-generatie drie seconden? Dan wacht de gebruiker drie seconden. Is de API van het boekhoudpakket traag? Dan wacht de gebruiker daar bovenop. Geeft die API een timeout? Dan krijgt de gebruiker een foutmelding — terwijl de PDF al wel gegenereerd was en de e-mail al verstuurd.
Synchrone ketens zijn fragiel. Een trage schakel vertraagt het hele proces. Een falende schakel breekt het hele proces. En het ergste: gedeeltelijke failures zijn lastig te herstellen. De PDF is verstuurd maar de boekhoudkoppeling is mislukt — wat is nu de status van die factuur? Dat soort vragen wil je niet beantwoorden in productie.
Bij een handvol gebruikers merk je hier weinig van. Maar zodra je SaaS groeit en tientallen gebruikers tegelijkertijd facturen versturen, lopen requests vast, workers raken bezet en je applicatie wordt traag voor iedereen. Cascading failures noemen we dat: een probleem in een extern systeem plant zich voort door je hele applicatie.
Events en queues
De oplossing is conceptueel simpel: scheid het registreren van een actie van het uitvoeren ervan. In plaats van alles in een HTTP-request te proppen, registreer je wat er moet gebeuren en verwerk je het op de achtergrond. Fire and forget.
De gebruiker klikt op "Verstuur factuur". Je applicatie slaat de factuur op, registreert dat er iets moet gebeuren en stuurt direct een bevestiging terug. De gebruiker ziet de bevestiging binnen milliseconden. Op de achtergrond worden de bijbehorende taken opgepakt: PDF genereren, e-mail versturen, boekhoudkoppeling synchroniseren. Elk onafhankelijk van elkaar, op hun eigen tempo, met hun eigen foutafhandeling.
Laravel maakt dit bijzonder toegankelijk. Het queue-systeem ondersteunt meerdere drivers — van database-queues voor eenvoudige setups tot Amazon SQS voor productieomgevingen op AWS. Je definieert een event, koppelt er listeners aan en dispatcht het. De rest handelt het framework af.
Events worden zo first-class citizens in je architectuur. Ze zijn niet langer bijzaak of afterthought, maar de ruggengraat van je applicatie. Elk belangrijk moment in je domein — een bestelling geplaatst, een gebruiker geregistreerd, een betaling ontvangen — wordt een event dat andere delen van je systeem kunnen oppikken en verwerken.
Een goed ontworpen systeem vertelt je niet alleen wat er is gebeurd, maar stelt elk onderdeel in staat om daar zelfstandig op te reageren.
Wanneer synchrone calls prima zijn
Niet alles hoeft asynchroon. Sterker nog: het onnodig asynchroon maken van simpele operaties voegt complexiteit toe zonder voordeel. Wees bewust in je keuze.
Standaard CRUD-operaties — een record aanmaken, bijwerken of verwijderen — zijn bijna altijd prima als synchrone calls. De gebruiker verwacht directe feedback en de operatie is snel genoeg om dat te bieden. Hetzelfde geldt voor leesoperaties: een pagina laden, een lijst ophalen, een detail-scherm tonen. Die data moet direct beschikbaar zijn; daar past geen queue tussen.
Validatie en autorisatie zijn per definitie synchroon. Je moet direct weten of een gebruiker rechten heeft, of invoer geldig is, of een resource bestaat. Daar hoort een onmiddellijk antwoord bij.
De vuistregel: als een operatie minder dan 200 milliseconden duurt, voorspelbaar is in doorlooptijd en geen afhankelijkheid heeft van externe systemen, is een synchrone call de juiste keuze. Voeg geen complexiteit toe die je niet nodig hebt.
Wanneer je events nodig hebt
Er zijn duidelijke signalen dat een operatie niet in een synchrone request thuishoort:
- Externe API-calls — Communicatie met payment providers, verzendplatformen, e-mailservices of boekhoudsystemen. Je hebt geen controle over hun responstijd of beschikbaarheid.
- Zware berekeningen — PDF-generatie, beeldverwerking, AI-inferentie, rapportages over grote datasets. Alles wat meer dan een seconde kost, hoort niet in een HTTP-request.
- Multi-step workflows — Processen die meerdere stappen omvatten waarbij elke stap afzonderlijk kan slagen of falen. Een bestelflow met betaling, voorraadreservering, verzendlabel en bevestigingsmail is een klassiek voorbeeld.
- Taken die onafhankelijk kunnen falen — Als het mislukken van een e-mailnotificatie niet mag betekenen dat de hele factuur mislukt, moeten die twee operaties ontkoppeld zijn.
- Operaties die kunnen wachten — Zoekindexen bijwerken, analytics verwerken, caches opwarmen. De gebruiker hoeft daar niet op te wachten.
De gemene deler: zodra je te maken hebt met onvoorspelbare doorlooptijd, externe afhankelijkheden of taken die onafhankelijk van elkaar moeten kunnen slagen of falen, zijn events en queues de juiste keuze.
Patronen in de praktijk
Event-driven architectuur is meer dan events dispatchen en listeners schrijven. Er zijn beproefde patronen die je helpen om complexe systemen beheersbaar te houden.
Event sourcing
Bij event sourcing sla je niet de huidige staat van een entiteit op, maar de reeks gebeurtenissen die tot die staat hebben geleid. Een bestelling is niet een rij in een tabel met status "verzonden". Het is een reeks: bestelling geplaatst, betaling ontvangen, ingepakt, verzonden. De huidige staat leid je af uit die gebeurtenissen.
Het voordeel: je hebt een volledige audit trail, je kunt de staat op elk willekeurig moment reconstrueren en je kunt nieuwe projecties bouwen over historische data. Het nadeel: het is complex en niet voor elk domein geschikt. Gebruik het waar de geschiedenis van een entiteit daadwerkelijk waardevol is — financiele transacties, bestelprocessen, compliance-gevoelige data.
Het saga-patroon
Een saga coordineert een multi-step proces waarbij elke stap een eigen transactie is. Stel: een klant plaatst een bestelling. Stap 1: reserveer voorraad. Stap 2: verwerk betaling. Stap 3: genereer verzendlabel. Als stap 2 mislukt, moet stap 1 worden teruggedraaid (compensating action). De saga beheert die flow en weet bij elke stap wat er moet gebeuren als het misgaat.
In Laravel implementeer je dit vaak met een job chain of een state machine. Het belangrijkste is dat elke stap idempotent is — veilig om opnieuw uit te voeren — en dat er voor elke stap een compenserende actie gedefinieerd is.
Foutafhandeling en retry-strategieen
Jobs falen. Dat is geen uitzondering, dat is de norm. Een robuust queue-systeem is daarop ingericht. Laravel biedt out-of-the-box retry-mechanismen: je configureert hoeveel keer een job herhaald mag worden, met welke vertraging (exponential backoff) en wat er moet gebeuren als alle pogingen zijn uitgeput.
Dat laatste is cruciaal: de dead letter queue. Jobs die na alle retries nog steeds falen, worden apart opgeslagen voor handmatige inspectie. Je wilt die niet stilletjes laten verdwijnen. Een factuur die nooit bij het boekhoudpakket aankomt, is een probleem dat je wilt weten — niet een dat je wilt negeren.
Monitor je queues actief. Weet hoeveel taken er in de wachtrij staan, hoe lang de gemiddelde verwerkingstijd is en hoeveel taken falen. Real-time inzicht in je achtergrondprocessen is essentieel om problemen te signaleren voordat ze impact hebben op gebruikers.
Decoupling gaat niet over technologie. Het gaat over de vrijheid om elk onderdeel van je systeem onafhankelijk te laten evolueren, schalen en falen — zonder dat de rest omvalt.
Conclusie
Event-driven architectuur is geen alles-of-niets keuze. Het is een gereedschap dat je inzet waar het waarde toevoegt. Simpele CRUD blijft synchroon. Externe koppelingen, zware taken en multi-step processen verplaats je naar de achtergrond.
De winst is drieledig. Je gebruikers krijgen een snellere ervaring omdat ze niet wachten op achtergrondprocessen. Je systeem wordt robuuster omdat een falende externe service niet je hele applicatie platlegt. En je architectuur wordt flexibeler omdat je nieuwe functionaliteit kunt toevoegen door simpelweg een nieuwe listener aan een bestaand event te koppelen — zonder bestaande code aan te raken.
Begin klein. Identificeer de synchrone calls die het vaakst problemen veroorzaken — de trage API-koppelingen, de zware berekeningen, de fragiele ketens — en verplaats die naar queues. Bouw van daaruit verder. Een goed ontworpen event-driven architectuur groeit mee met je product, in plaats van het in de weg te zitten.
/Gerelateerde artikelen
SaaS bouwen: van MVP tot schaal
De stappen en keuzes bij het bouwen van een SaaS-product.
Multi-tenancy in Laravel
Hoe bouw je een SaaS met meerdere klanten op één codebase?
Idempotency bij API-koppelingen
Dubbele webhooks en retries robuust afhandelen.