<EbeneX/>
Architektur Architektur · Updated 11. März 2026

CQRS

Definition

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

Experte 3 Min. Lesezeit EN: Command Query Responsibility Segregation

Einfach erklärt

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, ValidierungGeschwindigkeit
SchemaNormalisiertDenormalisiert
SkalierungWeniger LastMehr Last
KomplexitätBusiness-LogikEinfache Abfragen

Technischer Deep Dive

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 (einfach, aber 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 (entkoppelt):

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
  (t=0)            Published            (t=100ms)
                    (t=10ms)
    
    └────── 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 CRUD❌ Overkill
Read >> Write (10:1+)✅ Read separat skalieren
Komplexe Reports✅ Denormalisierte Views
Event Sourcing✅ Natürliche Ergänzung
Echtzeit-Anforderungen⚠️ Eventual Consistency beachten

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.

Trennt Commands (Schreiben) von Queries (Lesen)

Ermöglicht unterschiedliche Optimierungen für Read und Write

Oft kombiniert mit Event Sourcing

High-Read-Systeme

Viele Leser, wenige Schreiber – Read-Seite separat skalieren

Komplexe Domains

Write-Modell für Business-Logik, Read-Modell für UI

Reporting

Denormalisierte Views für schnelle Reports

Event Sourcing

Events schreiben, Projections für Queries

Wann brauche ich CQRS?

Bei stark unterschiedlichen Read/Write-Anforderungen, komplexer Domain-Logik, hohen Skalierungsanforderungen. Für einfache CRUD-Apps ist es Overkill.

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 werden asynchron aktualisiert. Nach einem Write ist das Read-Modell kurzzeitig veraltet. Für viele Anwendungen akzeptabel, für manche nicht.

Erhöht CQRS die Komplexität?

Ja, deutlich. Zwei Modelle, Synchronisation, Eventual Consistency. Nur einsetzen wenn die Vorteile die Komplexität rechtfertigen.

Dein persönliches Share-Bild für Instagram – 1080×1080px, bereit zum Posten.