CQRS et Event Sourcing en TypeScript : 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 les applications CRUD classiques cessent de fonctionner lorsque le métier devient complexe

\n\n

Avez-vous déjà été confronté à une situation où une tâche simple — « afficher l'historique des modifications d'une commande » — se transforme en enfer ? Vous fouillez dans la base de données, regardez les logs, essayez de reconstituer qui et quand a modifié le statut. Puis arrive une demande : « Faites un audit de toutes les actions des utilisateurs des trois dernières années ». Et vous réalisez que l'architecture actuelle ne le supportera pas.

\n\n

Bonjour, je m'appelle PythonLib, et aujourd'hui nous allons analyser deux patrons puissants qui sauvent dans de telles situations : CQRS (Command Query Responsibility Segregation) et Event Sourcing. Nous allons écrire du code en TypeScript que vous pourrez utiliser immédiatement dans un projet réel.

\n\n

Si vous développez dans la fintech, les marketplaces, les CRM ou tout système où l'historique et l'audit sont importants — cet article est pour vous.

\n\n

Qu'est-ce que CQRS en termes simples

\n\n

Imaginez une bibliothèque. Habituellement, un seul bibliothécaire prête les livres, les réceptionne et tient le registre. Mais quand le nombre de visiteurs augmente, une file d'attente se forme. CQRS propose de séparer les responsabilités : un bibliothécaire ne fait que prêter (commandes), un second ne fait que réceptionner et enregistrer (requêtes).

\n\n

En programmation, cela signifie séparer les modèles de lecture et d'écriture. Au lieu d'un seul référentiel monolithique, nous créons :

\n\n
    \n
  • Command Model — pour modifier les données (Create, Update, Delete)
  • \n
  • Query Model — pour lire les données (Read)
  • \n
\n\n

Cela semble être une complication ? Oui, au début. Mais lorsque le système grandit, vous gagnez en flexibilité : vous pouvez optimiser la lecture séparément de l'écriture, utiliser différentes bases de données, du cache.

\n\n

Exemple en TypeScript : CQRS simple sans Event Sourcing

\n\n

Supposons que nous ayons un système de gestion des commandes. Commençons par définir les entités :

\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\n

Créons maintenant une commande et une requête :

\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\n

Et pour la lecture — un gestionnaire séparé :

\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                

Blogs

Book Recommendations