starker-xp/cqrses-bundle

A demo bundle

Installs: 7

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 1

Forks: 0

Open Issues: 0

Type:symfony-bundle

pkg:composer/starker-xp/cqrses-bundle

dev-master / 1.0.x-dev 2026-02-12 13:17 UTC

This package is not auto-updated.

Last update: 2026-02-15 01:29:13 UTC


README

Archive — Ce projet est un bundle Symfony expérimental développé dans le cadre d'une démarche de R&D personnelle autour du Domain-Driven Design (DDD), du CQRS (Command Query Responsibility Segregation) et de l'Event Sourcing. Il n'est plus maintenu et est conservé à titre de référence.

Contexte

Ce bundle a été conçu à une époque où je me formais au DDD en lisant des ouvrages de référence (notamment ceux d'Eric Evans et de Vaughn Vernon) tout en travaillant sous Symfony 2.x/3.x. L'objectif était double :

  1. Apprendre par la pratique — Implémenter de A à Z les patterns CQRS et Event Sourcing pour en comprendre les mécanismes internes.
  2. Fournir un scaffolding — Générer automatiquement la structure de code CQRS/ES pour un domaine métier donné, afin de réduire le boilerplate.

Analyse rétrospective réalisée en 2026 dans le cadre d'un nettoyage et d'une mise en archive de mes dépôts GitHub/GitLab.

Architecture

Le bundle implémente le cycle complet CQRS/Event Sourcing :

                    ┌─────────────────────────────────────────────┐
                    │                 WRITE SIDE                  │
                    │                                             │
  Requête ──► CommandBus ──► CommandHandler ──► Domain Aggregate  │
                    │                              │              │
                    │                         DomainEvent(s)      │
                    │                              │              │
                    │                    ┌─────────┴─────────┐    │
                    │                    ▼                   ▼    │
                    │              EventStore          Projection  │
                    │           (append-only)       (read model)  │
                    └─────────────────────────────────────────────┘

                    ┌─────────────────────────────────────────────┐
                    │                 READ SIDE                   │
                    │                                             │
  Requête ──► QueryBus ──► QueryHandler ──► ReadRepository ──► DB │
                    └─────────────────────────────────────────────┘

Structure du code

├── Command/                        # Commandes Symfony (CLI) pour le scaffolding
│   ├── GenererBaseCQRSCommand.php   # Génère la structure CQRS/ES complète d'un domaine
│   └── GenererCommandeCommand.php   # Génère une commande CQRS individuelle
│
├── DependencyInjection/            # Intégration Symfony (chargement config/services)
│
├── Generator/
│   └── CQRSESGenerator.php         # Moteur de génération basé sur les templates Twig
│
├── Resources/
│   ├── config/services.yml          # Déclaration de l'EventStore comme service
│   └── views/                       # 21 templates Twig pour le scaffolding
│       ├── Services/Command/        # Templates Command + Handler (Créer, Modifier, Supprimer)
│       ├── Services/Domain/         # Templates Agrégat, Events, DTO, Collection
│       ├── Services/Persistence/    # Templates Repository (écriture) + Projection
│       ├── Services/Query/          # Templates Query + QueryHandler
│       └── Resources/config/        # Template de déclaration de services YAML
│
└── Services/                        # Le framework CQRS/ES réutilisable
    ├── Bus/                         # Bus abstrait + interface Handler
    ├── Command/                     # CommandBus, CommandInterface, CommandHandlerInterface
    ├── Domain/                      # DomainEvents (agrégat), AbstractEvent, EventInterface, etc.
    ├── Persistence/                 # EventStore, AbstractProjection, AggregateHistorique
    └── Query/                       # QueryBus, QueryInterface, QueryHandlerInterface

Patterns et concepts implémentés

CQRS — Séparation Lecture / Écriture

  • CommandBus : dispatche une commande vers son handler via une convention de nommage (XxxCommandXxxHandler)
  • QueryBus : dispatche une query vers son handler pour la lecture
  • Séparation des connexions BDD : write_connection, read_connection, event_connection

Event Sourcing

  • DomainEvents (agrégat) : accumule les events en mémoire, les applique pour muter l'état interne
  • EventStore : persiste les events dans une table events (append-only)
  • Reconstitution : reconstruit un agrégat en rejouant ses events depuis l'EventStore
  • Snapshots : sauvegarde périodique de l'état de l'agrégat (tous les 5 events) pour optimiser la reconstitution
  • Projections : met à jour un read model dénormalisé de manière synchrone après chaque commit

Scaffolding

Pour un domaine Produit, la commande génère automatiquement :

php bin/console starkerxp:cqrs:generer:structure MonBundle Produit

Fichiers générés :

  • Command/Produit/ — CreerProduitCommand, CreerProduitHandler, ModifierProduitCommand/Handler, SupprimerProduitCommand/Handler
  • Domain/Produit/ — ProduitDomain (agrégat), ProduitDTO, ProduitPOPO, ProduitCollection, Events (AEteCree, AEteModifie, AEteSupprime)
  • Persistence/Ecriture/Produit/ — ProduitRepository, ProduitProjection
  • Persistence/Lecture/ — ProduitRepository (read-only)
  • Query/Produit/ — ProduitQuery, ProduitQueryHandler, ProduitListerQuery, ProduitListerQueryHandler
  • Resources/config/ — services.produit.yml (déclaration DI complète)

Compétences démontrées

  • Domain-Driven Design : agrégats, domain events, value objects, repositories
  • CQRS : séparation stricte des responsabilités lecture/écriture avec bus dédiés
  • Event Sourcing : persistance par events, reconstitution, snapshots, projections
  • Symfony : création de bundle, DependencyInjection, commandes console, générateur de code
  • Architecture logicielle : interfaces, classes abstraites, conventions, séparation en couches

Limitations connues

Ce projet étant un exercice de R&D, certaines limitations ont été identifiées avec le recul :

Limitation Détail
Pas de tests Aucun test unitaire ou fonctionnel n'accompagne le code
Sérialisation PHP L'EventStore utilise serialize()/unserialize() au lieu de JSON, ce qui pose des problèmes de sécurité et d'interopérabilité
Pas de gestion de concurrence Absence d'optimistic locking sur l'EventStore (pas de vérification de version au commit)
Projections synchrones Les projections sont exécutées de manière synchrone dans la même transaction, ce qui ne scale pas
Convention de nommage fragile Le mapping Command→Handler repose sur un remplacement de chaîne dans le nom de classe
Compatibilité Symfony Conçu pour Symfony 2.x/3.x — incompatible avec Symfony 4.2+ (ContainerAwareCommand déprécié, SensioGeneratorBundle abandonné, TreeBuilder API modifiée)

Prérequis (historiques)

  • PHP >= 5.3
  • Symfony 2.x ou 3.x
  • Doctrine DBAL
  • SensioGeneratorBundle
  • rhumsaa/uuid (aujourd'hui ramsey/uuid)

Licence

Projet personnel — usage libre à titre de référence.