Dans le domaine de l'architecture logicielle moderne, les applications CRUD traditionnelles (Create, Read, Update, Delete) atteignent souvent leurs limites face à une logique métier complexe et à des exigences d'audit strictes. C'est ici que l'Event Sourcing intervient, non seulement comme un modèle, mais comme un changement de paradigme fondamental. En découplant l'état actuel du système de l'historique des modifications, les développeurs peuvent construire des systèmes plus transparents, flexibles et robustes.
Qu'est-ce que l'Event Sourcing ?
Au cœur de l'Event Sourcing se trouve un modèle architectural où l'état d'une application n'est pas stocké directement, mais dérivé d'une séquence d'événements. Au lieu de sauvegarder l'état actuel d'un objet (par exemple, OrderStatus = Shipped), vous enregistrez le fait qu'un événement s'est produit (par exemple, OrderShipped).
Imaginez un compte bancaire. Dans un système traditionnel, vous mettriez à jour la colonne du solde dans un enregistrement de base de données. Dans un système basé sur l'Event Sourcing, vous tenez un registre de chaque dépôt et retrait. Pour connaître le solde actuel, il vous suffit de sommer ces événements. Cette approche transforme la logique de votre application en un journal immuable en écriture seule (append-only log), fournissant ainsi une piste d'audit complète par défaut.
Pourquoi choisir l'Event Sourcing ?
Le principal avantage de l'Event Sourcing est la capacité de reconstruire l'état d'une entité à n'importe quel moment. Cela est inestimable pour le débogage, l'audit et la conformité réglementaire. De plus, cela s'aligne naturellement avec le Domain-Driven Design (DDD), car les événements représentent souvent des jalons commerciaux significatifs.
Cependant, cela n'est pas sans complexité. L'interrogation des données devient plus complexe car votre magasin d'événements (event store) est optimisé pour les écritures, et non pour les lectures. C'est pourquoi l'Event Sourcing est souvent associé au CQRS (Command Query Responsibility Segregation). Vous utilisez le magasin d'événements pour les écritures et une base de données de lecture optimisée séparée (comme un magasin SQL ou NoSQL) pour les requêtes.
Mise en œuvre de l'Event Sourcing : un exemple de code
Examinons une implémentation simplifiée en Java pour démontrer comment l'état est dérivé des événements. Nous créerons une racine d'agrégat simple qui gère une commande.
public class Order {
private String orderId;
private OrderStatus status;
private List<ObjectEvent> events = new ArrayList<>();
// Constructeur
public Order(String orderId) {
this.orderId = orderId;
this.status = OrderStatus.CREATED;
// Enregistrer l'événement initial
applyEvent(new OrderCreatedEvent(orderId));
}
// Gestionnaire de commande
public void ship() {
if (this.status != OrderStatus.PACKED) {
throw new IllegalStateException("Order must be packed before shipping");
}
applyEvent(new OrderShippedEvent(orderId, Instant.now()));
}
// Appliquer un événement à l'état actuel
private void applyEvent(ObjectEvent event) {
events.add(event);
event.apply(this);
}
// Reconstruire l'état à partir des événements (utilisé lors du chargement depuis l'Event Store)
public void replay(List<ObjectEvent> historicalEvents) {
this.events = new ArrayList<>();
for (ObjectEvent event : historicalEvents) {
applyEvent(event);
}
}
}
// Interface pour tous les événements
interface ObjectEvent {
void apply(Order order);
}
// Exemple d'événement
class OrderShippedEvent implements ObjectEvent {
private String orderId;
private Instant shippedAt;
public OrderShippedEvent(String orderId, Instant shippedAt) {
this.orderId = orderId;
this.shippedAt = shippedAt;
}
@Override
public void apply(Order order) {
order.status = OrderStatus.SHIPPED;
System.out.println("Order " + orderId + " shipped at " + shippedAt);
}
}
Dans cet exemple, la classe Order ne stocke pas son historique. Elle ne contient que l'état actuel. Lorsque nous devons charger la commande à partir de la persistance, nous passons la liste historique des événements à la méthode replay, permettant à l'objet de se reconstruire avec précision.
Défis et meilleures pratiques
Bien que puissant, l'Event Sourcing introduit des défis. La gestion des versions est critique ; si le schéma de vos événements change, vous devez gérer la compatibilité ascendante. De plus, les performances peuvent souffrir si vous rejouez fréquemment des milliers d'événements. Pour atténuer ce problème, les développeurs mettent souvent en place des instantanés (snapshots) : ils sauvegardent périodiquement l'état de l'agrégat pour éviter de rejouer les événements plus anciens.
Conclusion
L'Event Sourcing est un outil puissant pour des domaines de problèmes spécifiques, en particulier ceux qui nécessitent une haute intégrité, un historique complexe et une flexibilité dans les rapports. Bien qu'il ajoute une surcharge au processus de développement, les avantages à long terme d'avoir un historique complet et immuable de votre domaine métier dépassent souvent les coûts. En comprenant et en appliquant ce modèle, les architectes peuvent construire des systèmes qui ne sont pas seulement fonctionnels, mais profondément éclairants.