Pourquoi CQRS et Event Sourcing ne sont pas que des mots à la mode
\n\nImaginez que votre code est un immense musée. Habituellement, nous ne conservons que la dernière version de l'exposition : "voici à quoi ressemble la commande maintenant". Mais que faire si nous voulons savoir comment la commande en est arrivée à cet état ? Qui a cliqué sur le bouton "Envoyer" ? Quand le statut a-t-il changé ? C'est ici qu'entrent en scène CQRS (Command Query Responsibility Segregation) et Event Sourcing en Kotlin.
\n\nDans ce guide, nous ne nous contenterons pas de copier la théorie de Wikipedia. Nous analyserons des exemples concrets, écrirons du code et comprendrons comment ces patrons transforment le chaos des opérations CRUD en une architecture structurée. Si vous en avez assez du "code spaghetti" et souhaitez construire des systèmes faciles à faire évoluer et à déboguer, vous êtes au bon endroit.
\n\nQu'est-ce que CQRS en termes simples
\n\nDans une architecture standard, nous utilisons les mêmes modèles pour lire et écrire les données. C'est comme si vous mangiez une soupe tout en la remuant avec la même cuillère. CQRS propose de séparer les responsabilités :
\n\n- \n
- Command (Commande) — modifie l'état. Requêtes d'écriture (Create, Update, Delete). \n
- Query (Requête) — lit l'état. Uniquement des opérations SELECT. \n
Pourquoi est-ce nécessaire ? Imaginez une boutique en ligne. Lorsqu'un utilisateur passe une commande, nous devons vérifier le stock, déduire le produit, créer un enregistrement en base de données, envoyer un email. C'est une commande complexe. En revanche, lorsqu'un client consulte simplement la liste de ses commandes, c'est une requête simple. Les mélanger dans une même méthode est une voie sûre vers les bugs.
\n\nAvantages de CQRS en Kotlin
\n- \n
- Performance : possibilité d'optimiser la lecture et l'écriture différemment (bases de données, caches, index distincts). \n
- Sécurité : séparation des droits d'accès (l'admin écrit, l'utilisateur lit). \n
- Évolutivité : les commandes et les requêtes peuvent être réparties sur différents serveurs. \n
Event Sourcing : conserver l'historique, pas les faits
\n\nSi CQRS sépare les opérations, Event Sourcing modifie la manière de stocker les données. Au lieu de stocker l'état actuel (par exemple, "solde : 1000 euros"), nous stockons une séquence d'événements : "Dépôt de 500", "Achat de 200", "Déduction de frais de 50".
\n\nL'état actuel n'est que le résultat de l'application de tous les événements dans l'ordre. Cela offre un super-pouvoir : nous pouvons "remonter" le temps, restaurer l'état à n'importe quelle date ou analyser le comportement des utilisateurs.
\n\nExemple concret : un compte bancaire
\n// Événement\ndata class MoneyDeposited(val amount: BigDecimal, val timestamp: Instant)\ndata class MoneyWithdrawn(val amount: BigDecimal, val timestamp: Instant)\n\n// Agrégat (entité)\nclass BankAccount {\n private var balance: BigDecimal = BigDecimal.ZERO\n private val events = mutableListOf<Any>()\n\n fun deposit(amount: BigDecimal) {\n if (amount <= BigDecimal.ZERO) throw IllegalArgumentException("Le montant doit être positif")\n val event = MoneyDeposited(amount, Instant.now())\n apply(event)\n events.add(event)\n }\n\n fun withdraw(amount: BigDecimal) {\n if (amount > balance) throw IllegalStateException("Fonds insuffisants")\n val event = MoneyWithdrawn(amount, Instant.now())\n apply(event)\n events.add(event)\n }\n\n private fun apply(event: Any) {\n when (event) {\n is MoneyDeposited -> balance += event.amount\n is MoneyWithdrawn -> balance -= event.amount\n }\n }\n\n fun getBalance(): BigDecimal = balance\n fun getUncommittedEvents(): List<Any> = events.toList()\n}\n\nComment CQRS et Event Sourcing fonctionnent ensemble
\n\nC'est un duo classique. CQRS gère la séparation des canaux, tandis que Event Sourcing s'occupe du stockage