Die Trennung von Lese- und Schreiboperationen – bekannt als Command Query Responsibility Segregation (CQRS) – wird oft mit überflüssiger Infrastruktur wie Kafka oder Event Sourcing assoziiert. Doch in der Praxis reicht ein einfacher Ansatz mit Go aus: Separate Modelle für Änderungen und Abfragen, die unabhängig optimiert werden können.
Die Grundidee von CQRS
CQRS bedeutet zunächst nur, dass Befehle (Commands) und Abfragen (Queries) klar voneinander getrennt werden. Ein Befehl ändert den Zustand eines Systems und sollte keine Daten zurückgeben, während eine Abfrage Daten liest, ohne den Zustand zu verändern. Wie Three Dots Labs betont, liegt der Nutzen darin, dass beide Operationen unterschiedliche Anforderungen erfüllen müssen: Befehle benötigen Validierung und Geschäftsregeln, Abfragen dagegen optimierte Datenstrukturen für Geschwindigkeit und Flexibilität.
Ein häufiger Fehler ist der Glaube, CQRS erfordere automatisch separate Datenbanken oder asynchrone Protokolle. Doch wie Microsoft in seiner offiziellen Anleitung erklärt, ist die Trennung der Modelle zunächst eine Frage der Architektur – nicht der Infrastruktur. In vielen Fällen reicht es aus, wenn Befehle und Abfragen dieselbe Datenbank nutzen, aber unterschiedliche Handler verwenden.
Die Namensgebung spielt dabei eine zentrale Rolle. Statt technischer Begriffe wie "Create" oder "Update"" sollte man operationelle Intentionen wählen, etwa "BuchungStornieren" oder "TrainingPlanen"*. In Go wird dies besonders deutlich, da Handler- und Typennamen direkt aus den Geschäftsprozessen abgeleitet werden können.
Wann CQRS sinnvoll ist
CQRS wird erst relevant, wenn ein CRUD-System mehrere Aufgaben gleichzeitig erfüllt – und dabei an Grenzen stößt. Microsoft nennt typische Symptome:
- - Lese- und Schreibmodelle weichen stark voneinander ab.
- - Gleichzeitig eintretende Änderungen führen zu Blockaden.
- - Komplexe Abfragen verlangsamen die Leseperformance.
- - Sicherheitsregeln werden durch gemischte Verantwortlichkeiten unübersichtlich.
Besonders betroffen sind Systeme mit hohen Anforderungen an Konsistenz (z. B. Buchungssysteme) oder solche, die stark unterschiedliche Ladezeiten für Lesen und Schreiben benötigen. Hier ermöglicht CQRS, die Schreibseite streng an Geschäftsregeln auszurichten, während die Leseseite auf Performance und Benutzerfreundlichkeit optimiert wird.
Ein weiterer Vorteil ist die bessere Teamarbeit: Durch klare Schnittstellen zwischen Befehlen und Abfragen wird der Code leichter verständlich. Neue Entwickler können sich schneller einarbeiten, da sie entweder an Befehlen (z. B. Validierung) oder Abfragen (z. B. Datenaufbereitung) arbeiten – ohne sich durch monolithischen Code kämpfen zu müssen.
Praktische Umsetzung in Go
Die Implementierung von CQRS in Go ist dank der Spracheigenschaften besonders einfach. Go fördert explizite Grenzen, kleine Schnittstellen und paketorientierte Strukturen – alles, was für eine saubere Trennung von Befehlen und Abfragen benötigt wird.
Ein minimaler Ansatz könnte so aussehen:
type CommandHandler interface {
Handle(ctx context.Context, cmd interface{}) error
}
type QueryHandler interface {
Handle(ctx context.Context, query interface{}) (interface{}, error)
}
// Beispiel für einen Befehl
func (h *CreateOrderHandler) Handle(ctx context.Context, cmd *CreateOrder) error {
// Validierung und Geschäftslogik
if err := validateOrder(cmd); err != nil {
return err
}
// Datenbankoperationen
return h.repo.SaveOrder(ctx, cmd)
}
// Beispiel für eine Abfrage
func (h *GetOrdersHandler) Handle(ctx context.Context, query *GetOrders) (*[]OrderView, error) {
// Optimierte Abfrage für die Darstellung
return h.repo.GetOrders(ctx, query.Filters)
}Wichtig ist, dass beide Handler auf derselben Datenbank arbeiten können, solange die Abfragen gezielt optimiert werden. Erst wenn die Performance oder die Komplexität es erfordert, sollten separate Datenbanken oder sogar unterschiedliche Speichertechnologien (z. B. Redis für Caching) in Betracht gezogen werden.
Vorteile und Kosten im Vergleich
Die Vorteile einer CQRS-Implementierung in Go sind messbar:
- - Klarere Architektur: Kleine, fokussierte Handler ersetzen monolithischen Code.
- - Bessere Performance: Abfragen können durch DTOs (Data Transfer Objects) oder materialisierte Views optimiert werden.
- - Einfachere Wartung: Änderungen an Befehlen oder Abfragen betreffen jeweils nur einen Teil des Systems.
Die Nachteile liegen vor allem in der zusätzlichen Komplexität:
- - Mehr Code: Jede Operation benötigt einen eigenen Handler.
- - Potenzielle Inkonsistenzen: Wenn Befehle und Abfragen nicht synchronisiert sind, können Daten auseinanderlaufen.
- - Überdimensionierung: Viele Systeme benötigen CQRS erst, wenn sie bereits unter Last stehen.
Martin Fowler warnt zu Recht: Für die meisten Anwendungen ist CQRS nicht notwendig. Der richtige Zeitpunkt für die Einführung ist dann gekommen, wenn klassisches CRUD an seine Grenzen stößt – nicht wenn es theoretisch elegant erscheint.
Fazit: CQRS als Werkzeug, nicht als Dogma
CQRS ist kein Architektur-Patent, sondern ein Werkzeug, um spezifische Probleme zu lösen. In Go lässt es sich besonders elegant umsetzen, weil die Sprache klare Strukturen und explizite Grenzen fördert. Der Schlüssel liegt darin, mit einem einfachen Modell zu beginnen und erst bei Bedarf komplexere Lösungen wie Event Sourcing oder separate Datenbanken einzuführen.
Für Teams, die mit wachsenden Anforderungen kämpfen, kann CQRS der Unterschied zwischen einem wartbaren System und einem unübersichtlichen Monolithen sein. Doch wie bei jedem Entwurfsmuster gilt: Nicht die Theorie entscheidet, sondern das konkrete Problem.
KI-Zusammenfassung
Go’da CQRS uygulamak, komut ve sorguları ayırarak sistem performansını artırır. Basit başlayın, gerektiğinde geliştirin. Ayrıntılar ve örneklerle birlikte.