Warum CQRS und Event Sourcing mehr als nur Modewörter sind
\n\nStellen Sie sich vor, Ihr Code sei ein riesiges Museum. Normalerweise speichern wir nur die letzte Version eines Exponats: "So sieht die Bestellung jetzt aus". Aber was, wenn wir wissen wollen, wie die Bestellung in diesen Zustand geraten ist? Wer hat den "Senden"-Button gedrückt? Wann hat sich der Status geändert? Genau hier kommen CQRS (Command Query Responsibility Segregation) und Event Sourcing in Kotlin ins Spiel.
\n\nIn diesem Leitfaden kopieren wir nicht einfach die Theorie aus Wikipedia. Wir analysieren reale Beispiele, schreiben Code und verstehen, wie diese Muster das Chaos von CRUD-Operationen in eine saubere Architektur verwandeln. Wenn Sie "Spaghetti-Code" leid sind und Systeme bauen möchten, die sich leicht skalieren und debuggen lassen, sind Sie hier genau richtig.
\n\nWas ist CQRS in einfachen Worten
\n\nIn der Standardarchitektur verwenden wir dieselben Modelle zum Lesen und Schreiben von Daten. Das ist, als würden Sie Suppe essen und gleichzeitig mit demselben Löffel umrühren. CQRS schlägt vor, die Verantwortlichkeiten zu trennen:
\n\n- \n
- Command (Befehl) – ändert den Zustand. Schreiboperationen (Create, Update, Delete). \n
- Query (Abfrage) – liest den Zustand. Nur SELECT-Operationen. \n
Warum ist das nötig? Stellen Sie sich einen Online-Shop vor. Wenn ein Benutzer eine Bestellung aufgibt, müssen wir das Lager prüfen, den Artikel abbuchen, einen Datenbankeintrag erstellen, eine E-Mail senden. Das ist ein komplexer Befehl. Wenn ein Kunde hingegen nur seine Bestellliste ansieht, ist das eine einfache Abfrage. Beides in einer Methode zu vermischen, ist der sichere Weg zu Fehlern.
\n\nVorteile von CQRS in Kotlin
\n- \n
- Leistung: Lesen und Schreiben können unterschiedlich optimiert werden (verschiedene Datenbanken, Caches, Indizes). \n
- Sicherheit: Trennung der Zugriffsrechte (Admin schreibt, Benutzer liest). \n
- Skalierbarkeit: Befehle und Abfragen können auf verschiedene Server verteilt werden. \n
Event Sourcing: Geschichte bewahren, nicht Fakten
\n\nWährend CQRS die Operationen trennt, ändert Event Sourcing die Art der Datenspeicherung. Anstatt den aktuellen Zustand zu speichern (z. B. "Kontostand: 1000 Euro"), speichern wir eine Abfolge von Ereignissen: "Einzahlung von 500", "Kauf von 200", "Abbuchung von 50 Gebühren".
\n\nDer aktuelle Zustand ist einfach das Ergebnis der Anwendung aller Ereignisse in der richtigen Reihenfolge. Das verleiht Superkräfte: Wir können die Zeit zurückdrehen, den Zustand zu jedem beliebigen Datum wiederherstellen oder das Benutzerverhalten analysieren.
\n\nBeispiel aus dem echten Leben: Bankkonto
\n// Ereignis\ndata class MoneyDeposited(val amount: BigDecimal, val timestamp: Instant)\ndata class MoneyWithdrawn(val amount: BigDecimal, val timestamp: Instant)\n\n// Aggregat (Entität)\nclass BankAccount {\n private var balance: BigDecimal = BigDecimal.ZERO\n private val events = mutableListOf<Any>()\n\n fun deposit(amount: BigDecimal) {\n if (amount <= BigDecimal.ZERO) throw IllegalArgumentException("Der Betrag muss positiv sein")\n val event = MoneyDeposited(amount, Instant.now())\n apply(event)\n events.add(event)\n }\n\n fun withdraw(amount: BigDecimal) {\n if (amount > balance) throw IllegalStateException("Nicht genügend Guthaben")\n val event = MoneyWithdrawn(amount, Instant.now())\n apply(event)\n events.add(event)\n }\n\n private fun apply(event: Any) {\n when (event) {\n is MoneyDeposited -> balance += event.amount\n is MoneyWithdrawn -> balance -= event.amount\n }\n }\n\n fun getBalance(): BigDecimal = balance\n fun getUncommittedEvents(): List<Any> = events.toList()\n}\n\nWie CQRS und Event Sourcing zusammenarbeiten
\n\nDies ist das klassische Tandem. CQRS ist für die Trennung der Kanäle verantwortlich, während Event Sourcing für die Speicherung zuständig ist.