CQRS

Wie getrennte Schreib- und Lesewege komplexe Fachlogik und schnelle Ansichten vereinbaren – samt bewusster Umgang mit Aktualität.

Ein Architektur-Pattern, das Lese- und Schreiboperationen trennt – unterschiedliche Modelle für Commands (Änderungen) und Queries (Abfragen).

Experte2 Min Lesezeit

Erklärung starten

Auf einen Blick

3 Punkte

  1. Trennt Commands (Schreiben) von Queries (Lesen)
  2. Ermöglicht unterschiedliche Optimierungen für Read und Write
  3. Oft kombiniert mit Event Sourcing

Sofortantwort

CQRS einfach erklärt

Trennung von Lese- und Schreibmodellen für gezielte Optimierung.

Kurz gesagt
Trennt Commands (Schreiben) von Queries (Lesen)
Typischer Einsatz
High-Read-Systeme, Komplexe Domains, Reporting
Wichtig zu wissen
Oft kombiniert mit Event Sourcing

CQRS im Überblick

CQRS trennt deine Anwendung in zwei Teile: Einen für Änderungen (Commands) und einen für Abfragen (Queries). Jeder Teil kann unabhängig optimiert werden.

Traditionell (ein Modell für alles):

┌─────────────────────────────────┐
│           Application           │
│  ┌─────────────────────────┐   │
│  │      Single Model       │   │
│  │  (Read + Write)         │   │
│  └─────────────────────────┘   │
│              ↓                  │
│  ┌─────────────────────────┐   │
│  │       Database          │   │
│  └─────────────────────────┘   │
└─────────────────────────────────┘

CQRS (getrennte Modelle):

┌─────────────────────────────────────────────┐
│                Application                   │
│  ┌─────────────┐     ┌─────────────┐        │
│  │  Commands   │     │   Queries   │        │
│  │  (Write)    │     │   (Read)    │        │
│  └──────┬──────┘     └──────┬──────┘        │
│         ↓                   ↓               │
│  ┌─────────────┐     ┌─────────────┐        │
│  │ Write DB    │ ──→ │  Read DB    │        │
│  │ (normalized)│     │(denormalized)│       │
│  └─────────────┘     └─────────────┘        │
└─────────────────────────────────────────────┘

Warum trennen?

AspektWrite-SeiteRead-Seite
OptimierungKonsistenz, Validierungschnelle und passende Abfragen
Schemaoft stärker normalisiertoft denormalisiert oder view-orientiert
Skalierungabhängig von Schreiblastabhängig von Leselast
KomplexitätBusiness-LogikUI-, Reporting- oder API-nahe Views

Technisch betrachtet

Commands und Queries

Command (ändert Zustand):

@dataclass
class CreateOrderCommand:
    customer_id: str
    items: List[OrderItem]

@dataclass
class AddItemCommand:
    order_id: str
    sku: str
    quantity: int

class CommandHandler:
    def handle_create_order(self, cmd: CreateOrderCommand):
        order = Order.create(cmd.customer_id, cmd.items)
        self.repository.save(order)
        # Kein Return-Wert (oder nur ID)

Query (liest Zustand):

@dataclass
class GetOrderQuery:
    order_id: str

@dataclass
class ListOrdersQuery:
    customer_id: str
    status: Optional[str] = None

class QueryHandler:
    def handle_get_order(self, query: GetOrderQuery) -> OrderDTO:
        return self.read_db.get_order(query.order_id)
    
    def handle_list_orders(self, query: ListOrdersQuery) -> List[OrderDTO]:
        return self.read_db.find_orders(
            customer_id=query.customer_id,
            status=query.status
        )

Synchronisation Write → Read

Option 1: Synchron (einfacher, aber stärker gekoppelt):

class OrderService:
    def create_order(self, cmd: CreateOrderCommand):
        # Write
        order = Order.create(cmd.customer_id, cmd.items)
        self.write_db.save(order)
        
        # Sync to Read (im selben Request)
        self.read_db.upsert_order_view(order.to_view())

Option 2: Asynchron via Events (entkoppelter, aber eventual consistent):

class OrderService:
    def create_order(self, cmd: CreateOrderCommand):
        order = Order.create(cmd.customer_id, cmd.items)
        self.write_db.save(order)
        
        # Event publizieren
        self.event_bus.publish(OrderCreatedEvent(order))

class OrderViewUpdater:
    @subscribe(OrderCreatedEvent)
    def handle(self, event: OrderCreatedEvent):
        # Read-Modell asynchron aktualisieren
        self.read_db.upsert_order_view(event.order.to_view())

Read-Modell optimieren

Write-Modell (normalisiert):

-- Mehrere Tabellen, Joins nötig
orders (id, customer_id, status, created_at)
order_items (id, order_id, product_id, quantity, price)
products (id, name, description)
customers (id, name, email)

Read-Modell (denormalisiert):

-- Eine Tabelle, alles drin
order_views (
    id,
    customer_name,
    customer_email,
    status,
    items_json,  -- [{name, quantity, price}, ...]
    total,
    created_at
)

Eventual Consistency

Timeline:
─────────────────────────────────────────────────→
    │                    │                    │
    ▼                    ▼                    ▼
  Write              Event              Read Updated
                    Published
    
    └────── mögliche Inconsistency Window ──────┘

Umgang damit:

  • UI zeigt optimistisch den erwarteten Zustand
  • Polling oder WebSocket für Updates
  • Für kritische Reads: Direkt aus Write-DB lesen

Wann CQRS?

SzenarioCQRS sinnvoll?
Einfaches CRUDmeist nicht nötig
Deutlich andere Read-/Write-Lastkann separate Skalierung ermöglichen
Komplexe Reportskann denormalisierte Views vereinfachen
Event Sourcinghäufig passende Ergänzung
Strenge Echtzeit- oder KonsistenzanforderungenEventual Consistency sorgfältig prüfen

Schritt für Schritt

CQRS nur dort einsetzen, wo die Trennung Nutzen stiftet

CQRS lohnt sich, wenn Schreiben und Lesen wirklich unterschiedliche Anforderungen haben. Die zusätzliche Komplexität muss sichtbar gerechtfertigt sein.

  1. Read- und Write-Bedarf vergleichen

    01

    Prüfe, ob Fachregeln, Skalierung, Datenform oder Zugriffsprofile deutlich voneinander abweichen.

    Write: Regeln & Konsistenz | Read: Ansicht & Geschwindigkeit
  2. Commands fachlich definieren

    02

    Ein Command beschreibt eine beabsichtigte Änderung; seine Validierung und mögliche Ablehnung gehören zur Schreibseite.

    Command → Validierung → Zustandsänderung / Event
  3. Read-Modelle für echte Ansichten bauen

    03

    Die Leseseite wird auf konkrete Oberflächen, Reports oder APIs zugeschnitten statt ein Datenbankspiegel zu sein.

    Ereignis / Änderung → Projektion → passende View
  4. Aktualität transparent machen

    04

    Wenn Ansichten asynchron aktualisiert werden, braucht die Produktlogik eine verständliche Regel für Zwischenzustände.

    Write bestätigt → View wird aktualisiert → Status sichtbar
  5. Betrieb und Wiederaufbau testen

    05

    Monitoring, Fehlertoleranz und Rebuilds der Leseansichten werden als Teil der Architektur geprüft.

    Lag → Alert → Reprocess → konsistente View

Konkretes Beispiel

Beispiel: Auftragsübersicht mit hoher Leselast

Mitarbeitende lesen ständig Statusansichten, während Änderungen strengen Freigabe- und Fachregeln unterliegen.

Unterschiedliche Anforderungen

Die Schreibseite muss valide Auftragsänderungen garantieren; das Dashboard braucht schnell filterbare, verständliche Übersichten.

Getrennte Modelle

Commands und Fachlogik schützen die Änderungen; eine Projektion baut eine optimierte Auftragsansicht mit einem klaren Aktualitätsstatus.

CQRS trennt Verantwortlichkeiten, nicht automatisch Teams oder Infrastruktur – die Trennung sollte dem Fachproblem folgen.

Einordnung

Vorteile und Grenzen

Das spricht dafür

  • Gezielte Optimierung: Fachregeln beim Schreiben und nutzernahe Ansichten beim Lesen können unabhängig gestaltet werden.
  • Klare Verantwortlichkeit: Änderungen und Abfragen haben unterschiedliche, leichter erklärbare Pfade.
  • Skalierbare Ansichten: Read-Modelle lassen sich auf bestimmte Zugriffs- und Reporting-Bedürfnisse zuschneiden.

Das solltest du beachten

  • Mehr bewegliche Teile: Synchronisation, Monitoring und Fehlerbehandlung erhöhen Architektur- und Betriebsaufwand.
  • Zwischenzustände möglich: Asynchrone Ansichten können nach einer Änderung kurz hinterherhinken.
  • Zu viel für Einfaches: Bei überschaubaren CRUD-Anwendungen ist ein gemeinsames Modell meist leichter zu warten.
Vertiefung · für Fortgeschrittene

Die getrennten Pfade von CQRS

Die Schreibseite schützt Fachregeln, die Leseseite liefert für Nutzer optimierte Datenansichten.

Wissenskarte

Eine beabsichtigte Zustandsänderung wird vor ihrer Annahme geprüft.

Die bewusste Trennung schafft Spielraum für Optimierung, verlangt aber einen klaren Umgang mit Konsistenz und Betrieb. Fachliche Änderung gliedert sich in: Command, Validierung, Write Model, Event / Update, Projektion, Read Model.

01Einsatzbereiche

Wann ist CQRS sinnvoll?

Geeignet für

  • High-Read-SystemeViele Leser, wenige Schreiber – Read-Seite separat skalieren
  • Komplexe DomainsWrite-Modell für Business-Logik, Read-Modell für UI
  • ReportingDenormalisierte Views für schnelle Reports
  • Event SourcingEvents schreiben, Projections für Queries

↑ Inhalt

02Werkzeuge

Womit CQRS umgesetzt wird

↑ Inhalt

Merksatz

CQRS ist wie ein Restaurant mit getrennter Küche und Ausgabe

Die Küche (Write) ist optimiert für Zubereitung, die Ausgabe (Read) für schnelles Servieren. Beide haben unterschiedliche Anforderungen und Optimierungen.

  1. Trennt Commands (Schreiben) von Queries (Lesen)
  2. Ermöglicht unterschiedliche Optimierungen für Read und Write
  3. Oft kombiniert mit Event Sourcing

04Redaktion

Herkunft und Stand

Redaktion und Aktualität

Ebenex RedaktionRedaktion

Veröffentlicht
Aktualisiert

Dieses Feld entwickelt sich schnell. Oben stehen Veröffentlichung und letzte Änderung; ein Prüfdatum kommt dazu, sobald die Erklärung nach ihrer letzten Änderung geprüft wurde.

↑ Inhalt

05FAQ

Häufige Fragen zu CQRS

Wann brauche ich CQRS?

Bei stark unterschiedlichen Read/Write-Anforderungen, komplexer Domain-Logik, Reporting-Bedarf oder gezielter Skalierung. Für einfache CRUD-Apps ist der zusätzliche Aufwand meist nicht gerechtfertigt.

Ist CQRS dasselbe wie Event Sourcing?

Nein, aber sie passen gut zusammen. CQRS trennt Read/Write. Event Sourcing speichert Events statt Zustand. Man kann CQRS ohne Event Sourcing nutzen und umgekehrt.

Was ist Eventual Consistency bei CQRS?

Read-Modelle können asynchron aktualisiert werden. Nach einem Write kann das Read-Modell daher kurzzeitig veraltet sein. Ob das akzeptabel ist, hängt vom Use Case und Konsistenzbedarf ab.

Erhöht CQRS die Komplexität?

Ja, meist. Zwei Modelle, Synchronisation und mögliche Eventual Consistency erhöhen Architektur- und Betriebsaufwand. CQRS sollte nur eingesetzt werden, wenn die Vorteile diesen Aufwand rechtfertigen.

↑ Inhalt

06Weiterlernen

Was möchtest du als Nächstes verstehen?

  • Grundlage nachholen

    Microservices

    Anwendung besteht aus vielen kleinen, unabhängigen Services.

  • Technisch vertiefen

    Event Sourcing

    Zustand als Sequenz von Events speichern und bei Bedarf rekonstruieren.

↑ Inhalt