CQRS und Event Sourcing in Go: Architektur für komplexe Systeme

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Wenn gewöhnliches CRUD nicht mehr ausreicht: Einführung in CQRS und Event Sourcing

\n

Stellen Sie sich vor, Sie bauen ein Ticketbuchungssystem. Alles ist einfach, solange es zehn Benutzer gibt. Aber wenn es tausend werden und dann eine Million, treten Probleme auf: Datenbanken werden blockiert, Abfragen geraten in Konflikt, und der Änderungsverlauf kann nicht wiederhergestellt werden. Eine vertraute Situation?

\n

In der Welt der Go-Entwicklung, wo Leistung und Parallelität das A und O sind, helfen zwei Architekturmuster, diese Probleme zu lösen: CQRS (Command Query Responsibility Segregation) und Event Sourcing. Heute werden wir sie in der Praxis durchgehen – mit Code, Beispielen und realen Anwendungsszenarien.

\n\n

Was ist CQRS in einfachen Worten?

\n

CQRS ist die Trennung der Verantwortlichkeiten zwischen Befehlen (Schreiben) und Abfragen (Lesen). Im klassischen CRUD ist ein Datenmodell sowohl für das Erstellen als auch für das Lesen zuständig. In CQRS erstellen wir zwei separate Modelle:

\n
    \n
  • Command-Modell – zuständig für Zustandsänderungen (Erstellen, Aktualisieren, Löschen)
  • \n
  • Query-Modell – zuständig nur für das Lesen von Daten
  • \n
\n

Dies ermöglicht es, jede Seite unabhängig zu optimieren: Das Schreiben kann streng validiert sein, das Lesen denormalisiert und schnell. In Go ist dies dank Goroutinen und Kanälen besonders effektiv.

\n\n

Einfaches CQRS-Beispiel in Go

\n

Lassen Sie uns ein einfaches System zur Auftragsverwaltung erstellen. Beginnen wir mit den Befehlen:

\n\n
type CreateOrderCommand struct {\n    OrderID   string\n    UserID    string\n    ProductID string\n    Amount    float64\n}\n\ntype CommandHandler struct {\n    repo *OrderRepository\n}\n\nfunc (h *CommandHandler) HandleCreateOrder(ctx context.Context, cmd CreateOrderCommand) error {\n    order := Order{\n        ID:        cmd.OrderID,\n        UserID:    cmd.UserID,\n        ProductID: cmd.ProductID,\n        Amount:    cmd.Amount,\n        Status:    "pending",\n        CreatedAt: time.Now(),\n    }\n    return h.repo.Save(ctx, order)\n}
\n\n

Jetzt erstellen wir ein separates Modell für das Lesen:

\n\n
type OrderQuery struct {\n    repo *ReadRepository\n}\n\ntype OrderView struct {\n    OrderID    string\n    UserName   string\n    ProductName string\n    Total      float64\n    Status     string\n}\n\nfunc (q *OrderQuery) GetOrderSummary(ctx context.Context, orderID string) (*OrderView, error) {\n    return q.repo.GetOrderView(ctx, orderID)\n}
\n\n

Beachten Sie: In OrderView sind bereits denormalisierte Daten (Benutzername, Produktname) enthalten, die aus verschiedenen Quellen zusammengestellt werden. Dies beschleunigt das Lesen um ein Vielfaches.

\n\n

Event Sourcing: Nicht den Zustand, sondern Ereignisse aufzeichnen

\n

Event Sourcing ändert den Ansatz zur Datenspeicherung: Anstatt den aktuellen Zustand eines Objekts zu speichern, speichern wir die Abfolge von Ereignissen, die zu diesem Zustand geführt haben. Dies verleiht eine Superkraft: den vollständigen Änderungsverlauf und die Möglichkeit, den Zustand „zurückzuspulen“.

\n\n

Implementierung von Event Sourcing in Go

\n

Beginnen wir mit den grundlegenden Ereignistypen:

\n\n
type Event struct {\n    ID        string\n    AggregateID string\n    Type      string\n    Data      []byte\n    Timestamp time.Time\n    Version   int\n}\n\ntype EventStore interface {\n    Save(ctx context.Context, events []Event) error\n    Load(ctx context.Context, aggregateID string) ([]Event, error)\n}
\n\n

Jetzt implementieren wir das Aggregat – ein Objekt, das seinen Zustand aus Ereignissen wiederherstellt:

\n\n
type OrderAggregate struct {\n    ID     string\n    Status string\n    Items  []OrderItem\n    Version int\n}\n\nfunc (a *OrderAggregate) Apply(event Event) {\n    switch event.Type {\n    case "OrderCreated":\n        var data OrderCreatedData\n        json.Unmarshal(event.Data, &data)\n        a.ID = data.OrderID\n        a.Status = "created"\n    case "OrderConfirmed":\n        a.Status = "confirmed"\n    case "OrderShipped":\n        a.Stat                

Blogs

Buchempfehlungen