Warum gewöhnliche CRUD-Anwendungen nicht mehr funktionieren, wenn das Geschäft komplex wird
\n\nSind Sie schon einmal in eine Situation geraten, in der eine einfache Aufgabe – "den Änderungsverlauf einer Bestellung anzeigen" – zum Albtraum wird? Sie durchsuchen die Datenbank, schauen sich die Logs an und versuchen nachzuvollziehen, wer wann den Status geändert hat. Und dann kommt die Anfrage: "Erstellen Sie ein Audit aller Benutzeraktionen der letzten drei Jahre." Und Sie erkennen – die aktuelle Architektur hält das nicht aus.
\n\nHallo, mein Name ist PythonLib, und heute werden wir zwei leistungsstarke Muster besprechen, die in solchen Situationen helfen: CQRS (Command Query Responsibility Segregation) und Event Sourcing. Wir werden Code in TypeScript schreiben, der sofort in einem echten Projekt verwendet werden kann.
\n\nWenn Sie Fintech, Marktplätze, CRM oder Systeme entwickeln, bei denen Historie und Audit wichtig sind – dieser Artikel ist für Sie.
\n\nWas ist CQRS in einfachen Worten
\n\nStellen Sie sich eine Bibliothek vor. Normalerweise gibt ein Bibliothekar Bücher aus, nimmt sie zurück und führt die Buchhaltung. Aber wenn viele Besucher kommen, bildet sich eine Schlange. CQRS schlägt vor, die Aufgaben zu teilen: Ein Bibliothekar gibt nur aus (Befehle), der zweite nimmt nur zurück und führt Buch (Abfragen).
\n\nIn der Programmierung bedeutet dies die Trennung der Modelle für Lesen und Schreiben. Anstelle eines monolithischen Repositorys erstellen wir:
\n\n- \n
- Command Model – zum Ändern von Daten (Create, Update, Delete) \n
- Query Model – zum Lesen von Daten (Read) \n
Scheint das eine Verkomplizierung? Ja, am Anfang. Aber wenn das System wächst, gewinnen Sie Flexibilität: Sie können das Lesen unabhängig vom Schreiben optimieren, verschiedene Datenbanken und Caching verwenden.
\n\nBeispiel in TypeScript: Einfaches CQRS ohne Event Sourcing
\n\nAngenommen, wir haben ein Auftragsverwaltungssystem. Zuerst definieren wir die Entitäten:
\n\n// domain/order.ts\nexport interface Order {\n id: string;\n userId: string;\n items: OrderItem[];\n status: 'pending' | 'confirmed' | 'shipped' | 'cancelled';\n total: number;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface OrderItem {\n productId: string;\n quantity: number;\n price: number;\n}\n\n\nJetzt erstellen wir einen Befehl und eine Abfrage:
\n\n// application/commands/create-order.command.ts\nexport class CreateOrderCommand {\n constructor(\n public readonly userId: string,\n public readonly items: OrderItem[]\n ) {}\n}\n\n// application/commands/create-order.handler.ts\nimport { CommandHandler, ICommandHandler } from '@nestjs/cqrs';\nimport { OrderRepository } from '../repositories/order.repository';\n\n@CommandHandler(CreateOrderCommand)\nexport class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {\n constructor(private readonly orderRepo: OrderRepository) {}\n\n async execute(command: CreateOrderCommand): Promise<string> {\n const order: Order = {\n id: uuid(),\n userId: command.userId,\n items: command.items,\n status: 'pending',\n total: command.items.reduce((sum, item) => sum + item.price * item.quantity, 0),\n createdAt: new Date(),\n updatedAt: new Date()\n };\n await this.orderRepo.save(order);\n return order.id;\n }\n}\n\n\nUnd zum Lesen – ein separater Handler:
\n\n// application/queries/get-order.query.ts\nexport class GetOrderQuery {\n constructor(public readonly orderId: string) {}\n}\n\n// application/queries/get-order.handler.ts\nimport { QueryHandler, IQueryHandler } from '@nestjs/cqrs';\nimport { OrderReadRepository } from '../repositories/order-read.repository';\n\n@QueryHandler(GetOrderQuery)\nexport class GetOrderHandler implements IQueryHandler<GetOrderQuery> {\n constructor(private readonly readRepo: OrderReadRepository) {}\n\n async execute(query: GetOrderQuery): Promise<Order | null> {\n return this.readRepo.findById(query.orderId);\n }\n}\n