Software Architecture

Maîtriser CQRS et Event Sourcing : Le plan directeur pour des systèmes d'entreprise évolutifs

Les applications d'entreprise modernes font face à un double défi : elles doivent gérer des opérations d'écriture complexes et lourdes en état tout en supportant simultanément des requêtes de lecture flexibles et à haut débit. L'architecture CRUD (Create, Read, Update, Delete) traditionnelle peine souvent sous ce poids, entraînant un couplage étroit, des goulots d'étranglement de performance et une intégrité des données fragile. C'est ici que CQRS (Command Query Responsibility Segregation) et Event Sourcing brillent.

Bien qu'ils soient souvent discutés ensemble, ces modèles résolvent des problèmes distincts. CQRS sépare la logique de mise à jour de l'état (commandes) de la logique de récupération de l'état (requêtes). Event Sourcing change la manière dont l'état est persisté : au lieu de stocker l'état actuel, nous stockons une séquence d'événements décrivant chaque changement. Ensemble, ils forment une combinaison puissante pour construire des systèmes robustes, auditables et évolutifs.

Découpler les lectures et les écritures avec CQRS

Dans une architecture standard, un seul schéma de base de données sert à la fois aux besoins de lecture et d'écriture. À mesure que les exigences évoluent, les modèles d'écriture deviennent complexes avec des règles de cohérence strictes, tandis que les modèles de lecture nécessitent un indexation flexible et une dénormalisation pour des raisons de performance. CQRS répond à cela en introduisant des modèles séparés.

Prenons l'exemple d'une application bancaire. L'écriture d'une transaction nécessite une validation rigoureuse, des vérifications de concurrence et une cohérence immédiate. La lecture des soldes de compte peut nécessiter l'agrégation de données provenant de plusieurs sources pour une vue de tableau de bord. En séparant ces aspects, vous pouvez optimiser chaque modèle indépendamment.

Voici une représentation conceptuelle de la structure d'un gestionnaire CQRS :

class TransferMoneyCommandHandler {
    constructor(eventStore, accountRepository) {
        this.eventStore = eventStore;
        this.accountRepository = accountRepository;
    }

    async execute(command) {
        // 1. Récupérer la racine de l'agrégat
        const account = await this.accountRepository.getById(command.SourceAccountId);

        // 2. Appliquer la logique métier
        account.transfer(command.Amount, command.DestinationAccountId);

        // 3. Stocker les nouveaux événements (et non l'état)
        const events = account.getUncommittedEvents();
        await this.eventStore.append(events);
        
        // 4. Mettre à jour le modèle de lecture de manière asynchrone
        this.publishEventsToReadModel(events);
    }
}

Construire une piste d'audit avec Event Sourcing

Event Sourcing pousse le côté "écriture" de CQRS plus loin. Au lieu de sauvegarder l'état actuel d'un objet, vous sauvegardez une liste d'événements qui se sont produits. L'état actuel n'est qu'une projection de ces événements.

Cette approche offre une auditabilité inhérente. Vous n'avez pas besoin de créer des tables séparées pour suivre les changements ; l'historique est la donnée elle-même. Chaque action, de la connexion d'un utilisateur à un changement de prix, est capturée sous forme d'événement immuable. Cela est crucial pour la conformité réglementaire et le débogage de workflows complexes.

Par exemple, si un utilisateur affirme que le statut de sa commande est incorrect, vous pouvez rejouer l'intégralité de l'historique des événements pour cet ID de commande afin de voir exactement ce qui s'est passé, quand et par qui.

Reconstruction de l'état et projection

L'un des changements les plus significatifs dans cette architecture concerne la gestion des lectures. Puisque le magasin d'écriture ne contient que des événements, vous avez besoin d'un moyen de répondre aux requêtes efficacement. Cela se fait via des projections. Les projections sont des modèles de lecture qui s'abonnent aux événements et mettent à jour des magasins de données dénormalisés optimisés pour les requêtes.

Imaginez que vous devez afficher une liste de transactions récentes triées par date. Au lieu d'interroger le journal d'événements brut (ce qui est coûteux), vous disposez d'une projection qui écoute l'événement TransactionCreatedEvent et écrit les données pertinentes dans une base de données de lecture dédiée (comme Elasticsearch ou un magasin NoSQL).

Ce découplage permet à votre système de mettre à l'échelle horizontalement. Vous pouvez ajouter plus de réplicas en lecture pour gérer de forts volumes de requêtes sans impacter les performances d'écriture.

Considérations pratiques et défis

Bien que puissants, CQRS et Event Sourcing introduisent de la complexité. Vous devez gérer la cohérence éventuelle, en vous assurant que le modèle de lecture est finalement mis à jour après une commande d'écriture. Vous avez également besoin d'outils robustes pour gérer l'évolution du schéma des événements ; à mesure que votre logique métier évolue, votre schéma d'événements doit s'adapter sans rompre les capacités de rejouer les données historiques.

De plus, le débogage peut être plus difficile car l'état n'est pas directement visible dans la base de données. Cependant, le compromis en vaut la peine pour les systèmes qui exigent une haute évolutivité, des pistes d'audit strictes et une logique métier complexe.

Conclusion

CQRS et Event Sourcing ne sont pas des solutions miracles, mais ce sont des outils essentiels pour des défis architecturaux spécifiques. Ils permettent aux développeurs de construire des systèmes qui sont non seulement évolutifs et performants, mais aussi profondément informatifs grâce à leur historique immuable. Pour les développeurs intermédiaires à avancés cherchant à relever le défi des workflows métier complexes, maîtriser ces modèles est une étape critique vers la maturité architecturale.

Share: