CQRS: een patroon dat je dwingt om na te denken

Command Query Responsibility Segregation (CQRS) is een architectuurpatroon waarbij je lees- en schrijfoperaties strikt van elkaar scheidt. In plaats van een enkel model dat zowel data ophaalt als muteert, werk je met twee aparte modellen: een command model voor schrijfacties en een query model voor leesacties.

Dat klinkt misschien als onnodige complexiteit. Maar in de praktijk zie je dat veel systemen al worstelen met precies het probleem dat CQRS oplost: het datamodel dat optimaal is voor schrijven is zelden optimaal voor lezen.

Wanneer is CQRS zinvol?

CQRS is geen silver bullet. Het patroon voegt architectuurcomplexiteit toe en dat moet je terugverdienen. De volgende situaties maken CQRS waardevol:

  • Lees- en schrijfpatronen lopen sterk uiteen. Denk aan een systeem waar schrijfoperaties complex zijn (veel validatie, business rules) maar leesoperaties vooral denormalized views nodig hebben.
  • Performance-eisen verschillen per kant. Je wilt je leeskant onafhankelijk kunnen schalen van je schrijfkant.
  • Je domein is complex. In combinatie met Domain Driven Design helpt CQRS je om commands te modelleren als expliciete intenties vanuit het domein.
  • Audit en traceerbaarheid zijn belangrijk. Commands vormen een expliciet logboek van alle intenties in je systeem.

Omgekeerd: als je een simpele CRUD-applicatie bouwt met weinig business logic, dan is CQRS overkill. Wees eerlijk over de complexiteit van je domein.

De relatie met Event Sourcing

CQRS wordt vaak in een adem genoemd met Event Sourcing, maar het zijn twee onafhankelijke concepten. Je kunt CQRS prima toepassen zonder Event Sourcing, bijvoorbeeld met een relationele database voor commands en een gedenormaliseerde read store.

Wanneer je CQRS combineert met Event Sourcing krijg je wel een krachtige combinatie: events die uit de command-kant voortkomen worden gepubliceerd en gebruikt om read models (ook wel projections genoemd) op te bouwen. Dit geeft je maximale flexibiliteit om je leeskant te optimaliseren zonder je schrijfkant aan te passen.

Praktische tips voor implementatie

Begin klein. Pas CQRS niet toe op je hele systeem, maar op de bounded context waar het de meeste waarde oplevert. Vaak is dat het deel met de meeste business logic.

Houd je commands expliciet. Een command als UpdateCustomer is te vaag. Maak er ChangeCustomerAddress of DeactivateCustomer van. Hoe specifieker je commands, hoe beter je domein tot uiting komt in je code.

Accepteer eventual consistency. Als je lees- en schrijfkant aparte datastores gebruiken, is er altijd een korte vertraging. In de meeste gevallen is dat acceptabel, maar bespreek dit expliciet met je stakeholders.

Gebruik een message bus. Frameworks als MassTransit of NServiceBus maken het eenvoudig om commands en events te routeren. In .NET kun je ook starten met MediatR voor in-process CQRS zonder aparte infrastructuur.

Veelgemaakte fouten

De grootste fout die we tegenkomen is CQRS toepassen waar het niet nodig is. De op-een-na grootste fout is het te vroeg koppelen aan Event Sourcing. Begin met gescheiden modellen en een gedeelde database. Voeg pas Event Sourcing toe als je daar concrete behoefte aan hebt.

Een andere valkuil is te veel logica in de query-kant stoppen. Je query model hoort dom te zijn: het levert data op, meer niet. Alle business logic hoort in de command-kant thuis.

NForza en CQRS

Bij NForza passen we CQRS al jaren toe in missiekritische systemen. We helpen teams om te bepalen waar het patroon waarde toevoegt en waar het onnodige complexiteit introduceert. Onze ervaring leert dat de combinatie van DDD, CQRS en pragmatisch vakmanschap het verschil maakt tussen een architectuur die werkt en een die alleen op papier mooi is.