CQRS und Event Sourcing in JavaScript: 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

Warum CQRS und Event Sourcing nicht nur "für Auserwählte" sind?

\n\n

Wenn wir die Begriffe "CQRS" oder "Event Sourcing" hören, stellen sich viele JavaScript-Entwickler etwas aus der Welt der Enterprise-Architektur in Java oder C# vor. Komplexe Schemata, endlose Ereignisse, seltsame Speicher... Aber was, wenn ich Ihnen sage, dass diese Muster in gewöhnlichen Node.js-Anwendungen verwendet werden können (und sollten!)?

\n\n

In diesem Artikel werden wir verstehen, wie CQRS und Event Sourcing helfen, reale Probleme zu lösen: Datenkonflikte, komplexe Protokollierung, Skalierung und Fehlersuche. Und das alles – in reinem JavaScript, ohne Magie und komplexe Frameworks.

\n\n

Was ist CQRS (Command Query Responsibility Segregation)?

\n\n

Stellen Sie sich vor, Ihre Anwendung ist ein Restaurant. Kellner (Abfragen) und Köche (Befehle) arbeiten im selben Raum. Früher oder später werden sie sich gegenseitig stören. CQRS schlägt vor, diese Bereiche zu trennen: Kellner arbeiten im Saal (Lesen), Köche in der Küche (Schreiben).

\n\n

CQRS ist ein Prinzip der Trennung von Operationen in zwei Kanäle:

\n
    \n
  • Befehle (Commands) – ändern den Systemzustand (Create, Update, Delete)
  • \n
  • Abfragen (Queries) – lesen nur Daten (Read)
  • \n
\n\n

In einer klassischen REST-API verwenden wir normalerweise dasselbe Objekt zum Lesen und Schreiben. Zum Beispiel enthält UserController die Methoden GET /users/:id und POST /users. CQRS schlägt vor, sie in verschiedene Ebenen aufzuteilen.

\n\n

Einfaches CQRS-Beispiel in JavaScript

\n\n
// Befehl (Zustandsänderung)\nclass CreateUserCommand {\n    constructor(name, email) {\n        this.name = name;\n        this.email = email;\n    }\n}\n\nclass UserCommandHandler {\n    async handle(command) {\n        // Validierung, Geschäftslogik, Speicherung\n        return await db.users.insert({\n            name: command.name,\n            email: command.email,\n            created_at: new Date()\n        });\n    }\n}\n\n// Abfrage (nur Lesen)\nclass GetUserQuery {\n    constructor(userId) {\n        this.userId = userId;\n    }\n}\n\nclass UserQueryHandler {\n    async handle(query) {\n        // Nur Lesen, keine Änderungen!\n        return await db.users.findOne({ _id: query.userId });\n    }\n}
\n\n

Scheint es, dass dies den Code verkompliziert? Ja, auf den ersten Blick. Aber wenn Ihre Anwendung auf 50+ Endpunkte anwächst, wird die Trennung von Befehlen und Abfragen Sie bewahren vor:

\n
    \n
  • Unbeabsichtigten Datenänderungen in GET-Anfragen
  • \n
  • Problemen mit Caching (Abfragen lassen sich leicht cachen, Befehle nicht)
  • \n
  • Konflikten bei paralleler Arbeit mehrerer Entwickler
  • \n
\n\n

Event Sourcing: Jede Aktion merken

\n\n

Stellen Sie sich nun vor, dass Sie anstelle des aktuellen Zustands eines Benutzers alle Ereignisse speichern, die mit ihm passiert sind. Nicht "Ivan, 25 Jahre, Moskau", sondern eine Abfolge:

\n
    \n
  1. "Benutzer erstellt: Ivan"
  2. \n
  3. "Benutzer hat Alter auf 25 geändert"
  4. \n
  5. "Benutzer ist nach Moskau umgezogen"
  6. \n
\n\n

Event Sourcing ist ein Muster, bei dem jede Zustandsänderung als unveränderliches Ereignis gespeichert wird. Der aktuelle Zustand wird durch "Wiedergabe" aller Ereignisse berechnet.

\n\n

Implementierung eines Event Store in JavaScript

\n\n
class EventStore {\n    constructor() {\n        this.events = [];\n    }\n\n    append(aggregateId, event) {\n        this.events.push({\n            aggregateId,\n            event,\n            timestamp: new Date()\n        });\n    }\n\n    getEvents(aggregateId) {\n        return this.events.filter(e => e.aggregateId === aggregateId);\n    }\n}\n\n// Wiederherstellung des Zustands aus Ereignissen\nfunction rebuildState(events) {\n    return events.reduce((state, event) => {\n        switch(event.event.type) {\n            case 'USER_CREATED':\n                return { ...state, name: event.event.name, age: 0 };\n            case                

Blogs

Buchempfehlungen