CQRS et Event Sourcing en JavaScript : architecture pour systèmes complexes

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Pourquoi CQRS et Event Sourcing ne sont pas réservés à une élite ?

\n\n

Quand on entend les termes "CQRS" ou "Event Sourcing", beaucoup de développeurs JavaScript imaginent quelque chose venant du monde de l'architecture d'entreprise en Java ou C#. Des schémas complexes, des événements infinis, des stockages étranges... Mais si je vous disais que ces patrons peuvent (et doivent !) être utilisés dans des applications Node.js classiques ?

\n\n

Dans cet article, nous allons voir comment CQRS et Event Sourcing aident à résoudre des problèmes réels : conflits de données, journalisation complexe, mise à l'échelle et débogage. Et tout cela, en pur JavaScript, sans magie ni frameworks complexes.

\n\n

Qu'est-ce que CQRS (Command Query Responsibility Segregation) ?

\n\n

Imaginez que votre application est un restaurant. Les serveurs (requêtes) et les cuisiniers (commandes) travaillent dans la même salle. Tôt ou tard, ils vont se gêner mutuellement. CQRS propose de séparer ces zones : les serveurs travaillent en salle (lecture), et les cuisiniers en cuisine (écriture).

\n\n

CQRS est un principe de séparation des opérations en deux canaux :

\n
    \n
  • Commandes (Commands) — modifient l'état du système (Create, Update, Delete)
  • \n
  • Requêtes (Queries) — lisent uniquement les données (Read)
  • \n
\n\n

Dans une API REST classique, nous utilisons généralement le même objet pour la lecture et l'écriture. Par exemple, UserController contient les méthodes GET /users/:id et POST /users. CQRS propose de les répartir dans différentes couches.

\n\n

Exemple simple de CQRS en JavaScript

\n\n
// Commande (modification d'état)\nclass CreateUserCommand {\n    constructor(name, email) {\n        this.name = name;\n        this.email = email;\n    }\n}\n\nclass UserCommandHandler {\n    async handle(command) {\n        // Validation, logique métier, sauvegarde\n        return await db.users.insert({\n            name: command.name,\n            email: command.email,\n            created_at: new Date()\n        });\n    }\n}\n\n// Requête (lecture seule)\nclass GetUserQuery {\n    constructor(userId) {\n        this.userId = userId;\n    }\n}\n\nclass UserQueryHandler {\n    async handle(query) {\n        // Lecture seule, aucune modification !\n        return await db.users.findOne({ _id: query.userId });\n    }\n}
\n\n

Cela semble compliquer le code ? Oui, à première vue. Mais lorsque votre application atteindra 50+ points de terminaison, la séparation des commandes et des requêtes vous sauvera de :

\n
    \n
  • La modification accidentelle des données dans les requêtes GET
  • \n
  • Les problèmes de mise en cache (les requêtes sont faciles à mettre en cache, les commandes non)
  • \n
  • Les conflits lors du travail parallèle de plusieurs développeurs
  • \n
\n\n

Event Sourcing : mémoriser chaque action

\n\n

Imaginez maintenant qu'au lieu de stocker l'état actuel d'un utilisateur, vous stockez tous les événements qui lui sont arrivés. Pas "Ivan, 25 ans, Moscou", mais une séquence :

\n
    \n
  1. "Utilisateur créé : Ivan"
  2. \n
  3. "Utilisateur a changé son âge à 25"
  4. \n
  5. "Utilisateur a déménagé à Moscou"
  6. \n
\n\n

Event Sourcing est un patron où chaque modification d'état est sauvegardée comme un événement immuable. L'état actuel est calculé en "rejouant" tous les événements.

\n\n

Implémentation d'un Event Store en JavaScript

\n\n
class EventStore {\n    constructor() {\n        this.events = [];\n    }\n\n    append(aggregateId, event) {\n        this.events.push({\n            aggregateId,\n            event,\n            timestamp: new Date()\n        });\n    }\n\n    getEvents(aggregateId) {\n        return this.events.filter(e => e.aggregateId === aggregateId);\n    }\n}\n\n// Reconstruction de l'état à partir des événements\nfunction rebuildState(events) {\n    return events.reduce((state, event) => {\n        switch(event.event.type) {\n            case 'USER_CREATED':\n                return { ...state, name: event.event.name, age: 0 };\n            case                

Blogs

Book Recommendations