Introduction : Quand le CRUD ne suffit plus
\n\nImaginez que vous développez un système pour les échanges boursiers. Chaque seconde, des milliers d'ordres. Chaque ordre n'est pas simplement "insérer un enregistrement". C'est une vérification de solde, un recalcul de portefeuille, un audit, des notifications. Imaginez maintenant que vous deviez non seulement enregistrer l'état actuel, mais aussi reconstituer ce qui s'est passé à 14:35:22.003 hier. Le CRUD (Create, Read, Update, Delete) classique échoue ici. Bienvenue dans le monde du CQRS et de l'Event Sourcing en Java.
\n\nCes deux patrons ne sont pas de simples mots à la mode. Ce sont des outils qui permettent de construire des systèmes à haute performance, avec un audit complet et la possibilité de "remonter le temps". Dans cet article, nous allons voir comment implémenter le CQRS (Command Query Responsibility Segregation) et l'Event Sourcing en Java, en utilisant des frameworks modernes et du code propre. Sans blabla, avec des exemples.
\n\nQu'est-ce que le CQRS en termes simples
\n\nCQRS est la séparation des responsabilités entre les commandes (écriture) et les requêtes (lecture). Dans le CRUD classique, vous utilisez le même modèle pour la lecture et l'écriture. En CQRS, vous créez deux modèles distincts : un pour modifier les données (commandes), un autre pour les extraire (requêtes).
\n\nPourquoi est-ce nécessaire ?
\n- \n
- Performance : La lecture et l'écriture évoluent indépendamment. Vous pouvez avoir 10 réplicas pour la lecture et 2 pour l'écriture. \n
- Sécurité : Les commandes peuvent vérifier les règles métier, tandis que les requêtes se contentent de fournir les données. \n
- Flexibilité : Pour la lecture, vous pouvez utiliser des vues dénormalisées, pour l'écriture, des vues normalisées. \n
Exemple de code : Séparation des commandes et des requêtes
\n\nSupposons que nous ayons un système de gestion des commandes. Au lieu de faire OrderService.save(order), nous séparons :
// La commande est une intention de modifier l'état\npublic class CreateOrderCommand {\n private final String orderId;\n private final String customerId;\n private final List<OrderItem> items;\n\n // constructeur, getters...\n}\n\n// Gestionnaire de commande\n@Component\npublic class CreateOrderCommandHandler {\n @Autowired\n private OrderRepository orderRepository;\n\n public void handle(CreateOrderCommand command) {\n // Validation des règles métier\n if (command.getItems().isEmpty()) {\n throw new IllegalArgumentException("La commande doit contenir au moins un article");\n }\n Order order = new Order(command.getOrderId(), command.getCustomerId(), command.getItems());\n orderRepository.save(order);\n }\n}\n\n// La requête est une récupération de données\npublic class GetOrderQuery {\n private final String orderId;\n // constructeur, getters...\n}\n\n// Gestionnaire de requête\n@Component\npublic class GetOrderQueryHandler {\n @Autowired\n private OrderReadRepository orderReadRepository;\n\n public OrderDTO handle(GetOrderQuery query) {\n // Lecture directe depuis une base de données optimisée\n return orderReadRepository.findById(query.getOrderId());\n }\n}\n\nRemarquez : OrderRepository (écriture) et OrderReadRepository (lecture) sont des interfaces différentes. Elles peuvent pointer vers des bases de données ou des schémas différents.
Event Sourcing : Stocker non pas l'état, mais l'historique
\n\nEvent Sourcing est un patron où nous sauvegardons non pas l'état actuel d'un objet, mais la séquence d'événements qui ont conduit à cet état. Au lieu de "Commande n°123 : statut = LIVRÉE", nous stockons : "Événement 1 : Commande créée", "Événement 2 : Article ajouté", "Événement 3 : Commande payée", "Événement 4 : Commande livrée".
\n\nComment cela fonctionne en Java
\n\nChaque événement est un objet immuable (immutable). Nous les sauvegardons dans un Event Store (base de données spécialisée). L'état actuel de l'agrégat est calculé en rejouant (replaying) tous les événements.
\n\n// Événement de base\npublic interface Do