CQRS und Event Sourcing in PHP: 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 CRUD nicht mehr reicht: Einführung in CQRS und Event Sourcing

\n\n

Kennen Sie das? Ihre PHP-Anwendung wächst und wächst... und plötzlich wird aus einem harmlosen UserController ein Monster mit über 2000 Zeilen Code? Kommt Ihnen bekannt vor? Bugs vermehren sich, die Performance leidet, und jedes neue Feature löst bei Ihrem Team nervöse Zuckungen aus. Herzlichen Glückwunsch, Sie sind bereit für ernsthafte Architekturmuster.

\n\n

Heute tauchen wir in zwei mächtige Werkzeuge ein: CQRS (Command Query Responsibility Segregation) und Event Sourcing. Keine Angst vor den Fachbegriffen. Im Kern sind es elegante Methoden, um zu sagen: "Hören wir auf, Lesen und Schreiben in einen Topf zu werfen" und "Speichern wir nicht den aktuellen Zustand, sondern die gesamte Änderungshistorie".

\n\n

Dieser Artikel richtet sich an PHP-Entwickler, die die Grundlagen der OOP beherrschen und ihre Projekte auf das nächste Level bringen wollen. Sie lernen, wie Sie CQRS und Event Sourcing praktisch einsetzen, sehen echten Code und verstehen, wann diese Muster wirklich sinnvoll sind – und wann nicht.

\n\n

Was ist CQRS? Trennung von Befehlen und Abfragen

\n\n

Stellen Sie sich Ihren Code wie ein Restaurant vor. Normalerweise nimmt der Kellner (Controller) eine Bestellung auf (Schreibzugriff), rennt in die Küche (Datenbank), kocht das Gericht (verarbeitet Daten) und serviert es sofort (gibt Antwort zurück). Das funktioniert prima bei 10 Gästen. Aber bei 1000 Gästen bricht die Küche zusammen.

\n\n

CQRS (Command Query Responsibility Segregation) schlägt vor, die Verantwortung zu teilen: Einige Köche (Befehle) kochen nur, andere Kellner (Abfragen) servieren nur. Technisch bedeutet das, dass Sie unterschiedliche Modelle für Lese- und Schreibzugriffe verwenden.

\n\n

Basisimplementierung von CQRS in PHP

\n\n

Sehen wir uns den Code an. Wir beginnen mit einem einfachen Beispiel ohne Frameworks, um das Prinzip zu verstehen.

\n\n
// Ein Befehl ist ein DTO mit Daten zur Zustandsänderung\nclass CreateUserCommand {\n    public function __construct(\n        public readonly string $email,\n        public readonly string $name\n    ) {}\n}\n\n// Der Befehlshandler – hier findet die Geschäftslogik statt\nclass CreateUserHandler {\n    public function __construct(\n        private UserRepository $repository,\n        private EventDispatcher $dispatcher\n    ) {}\n\n    public function handle(CreateUserCommand $command): void {\n        $user = new User($command->email, $command->name);\n        $this->repository->save($user);\n        $this->dispatcher->dispatch(new UserCreatedEvent($user->getId()));\n    }\n}\n\n// Eine Abfrage ist ein DTO zum Abrufen von Daten\nclass GetUserQuery {\n    public function __construct(\n        public readonly int $userId\n    ) {}\n}\n\n// Der Abfragehandler – nur lesen, keine Änderungen\nclass GetUserHandler {\n    public function __construct(\n        private UserReadRepository $readRepository\n    ) {}\n\n    public function handle(GetUserQuery $query): ?UserDTO {\n        return $this->readRepository->findById($query->userId);\n    }\n}
\n\n

Beachten Sie: Wir haben zwei verschiedene Repositories. UserRepository ist für Schreibzugriffe zuständig (mit komplexer Geschäftslogik und Validierung), während UserReadRepository nur für Lesezugriffe da ist (einfache SQL-Abfragen oder sogar denormalisierte Tabellen).

\n\n

Die Command Bus

\n\n

In echten Projekten werden Befehle und Abfragen über einen Bus gesendet – einen zentralen Dispatcher, der automatisch den passenden Handler findet.

\n\n
// Einfachste Implementierung eines Command Bus\nclass SimpleCommandBus {\n    public function __construct(\n        private array $handlers = []\n    ) {}\n\n    public function registerHandler(string $commandClass, object $handler): void {\n        $this->handlers[$commandClass] = $handler;\n    }\n\n    public function dispatch(object $command): void {\n        $handler = $this->handlers[$co                

Blogs

Buchempfehlungen