Depuis plusieurs mois, l’Event Sourcing et les systèmes évènementiels sont sur toutes les lèvres. L’année passée a sans nul doute marqué l’avènement des systèmes dits « réactifs » ou « dirigés par les évènements ». Bien que leur présence ne date pas d’hier, peu d’applications en tirent aujourd’hui bénéfice. Durant la dernière Xebicon, Xebia a proposé un retour d’expérience sur la mise en place de ce pattern d’architecture au sein d’une banque d’investissement française que nous avons accompagnée durant plus de trois ans.
Cet article a pour but de revenir sur :
- Les concepts clés de l’Event Sourcing.
- Les points d’attention à suivre lors de son implémentation.
Qu’est-ce que l’Event Sourcing ?
L’Event Sourcing est un pattern d’architecture qui propose de se concentrer sur la séquence de changements d’état d’une application qui a amené cette dernière dans l’état où elle se trouve. L’idée n’est plus de savoir où nous sommes mais de garder trace du chemin parcouru pour y arriver.
En tant que pattern d’architecture, il possède un avantage de taille : vous ne dépendez d’aucun framework, langage ou librairie pour l’implémenter.
Quels que soient vos outils de prédilection, vous serez en mesure d’en tirer profit.
L’exemple le plus courant et le plus simple pour illustrer ce concept est votre compte bancaire :
Date | Débit | Crédit |
---|---|---|
14/05/2016 | 100€ | |
14/05/2016 | -20€ | |
16/05/2016 | -30€ | |
24/05/2016 | 380€ | |
01/06/2016 | -70€ | |
Total | -120€ | 480€ |
Solde | 360€ |
Votre banque ne se contente pas de vous communiquer le solde de ce dernier (état courant) et vous propose également toutes les opérations de débit et crédit qui se sont produites pour y parvenir (séquence de changements d’états). Ces changements d’états, ce sont les évènements. Ces derniers sont par nature « métiers » : ils doivent avoir du sens pour vos utilisateurs et experts métier.
Les plus perspicaces d’entre-vous se sont probablement rendu compte d’une propriété intéressante : ces opérations de débit/crédit, couplées au solde initial, permettent de retrouver l’état courant. C’est là l’un des avantages de l’Event Sourcing : on se concentre sur le comportement du système duquel on pourra déduire l’état actuel. Il est en effet bien plus facile de faire la synthèse d’un ensemble d’évènements plutôt que de retrouver ces derniers à partir d’un état.
Pourquoi l’utilise-t’on ?
Puisque l’on capture tous les évènements métiers produits, nous obtenons des capacités accrues en termes :
- d’audit : il est aisé de retrouver les effets d’actions du passé. Cela a beaucoup de valeur dans les contextes métiers où les régulateurs prennent une place importante (la finance, par exemple).
- d’analyse/debug : il est aisé de comprendre les évènements qui ont amené à tel ou tel bug. Quand on sait que le temps consacré à la correction d’un bug est majoritairement perdu dans des tentatives de reproductions infructueuses…
- de reprise de données : que ce soit en cas de panne (revenir dans l’état précédent la panne) ou pour reproduire un bug sur un autre environnement.
D’autre part, les systèmes à base d’évènements sont majoritairement asynchrones et basés sur des composants de type bus de messages. Cela permet de plus facilement les mettre à l’échelle en cas de besoin de performance même si cela dépendra nécessairement de votre architecture et de votre implémentation.
Enfin, les évènements sont un excellent moyen de synchroniser ou notifier différentes applications métiers, notamment au travers d’un autre pattern d’architecture nommé CQRS (Command Query Responsibility Segregation).
Les concepts clés
Note : les schémas ci-après sont inspirés de l’excellent blog de Jeremie Chassaing, voie portante et bien connue de l’Event Sourcing depuis de nombreuses années.
Ce dernier regorge de bons conseils sur l’implémentation d’un tel système, notamment dans ses liens avec le DDD (Domain Driven Design) et CQRS.
Votre système
L’implémentation du pattern Event Sourcing prend place au sein de votre application : celle-ci renferme notamment votre logique métier.
Elle est composée de 2 fonctions que nous allons détailler ensemble dans quelques paragraphes :
- la fonction de décision;
- la fonction d’évolution.
La commande
Une commande représente une intention provoquée par l’extérieur (utilisateur, système) sur votre application.
En ce sens, elle est exprimée à l’impératif pour marquer cette intention.
Ex: ReceivePrice
Elle contient tous les paramètres nécessaires à sa description (elle est autosuffisante).
L’évènement
Un évènement est un fait qui s’est produit dans le passé. Il est immuable, c’est à dire qu’il ne peut pas changer.
En ce sens, il est exprimé à la voie passive pour marquer cette immutabilité.
Ex: PriceReceived
Il contient tous les paramètres nécessaires à sa description (il est autosuffisant).
L’immutabilité d’un évènement est primordiale car c’est elle qui confère tous les avantages vus précédemment.
Nous verrons dans la suite de cet article que ces derniers s’évaporent sitôt qu’elle n’est plus assurée.
L’event store
L’event store (ou magasin d’évènements) sert à stocker l’ensemble des évènements produits par l’application.
Il est le plus souvent persisté afin de permettre les reprises de données évoquées précédemment.
La fonction de décision
Lorsqu’une commande parvient à votre système, elle active la fonction de décision.
La fonction de décision correspond au coeur de votre métier et contient l’ensemble de vos règles de gestion.
A partir de l’état courant ainsi que de la commande reçue, elle produit une liste d’un ou plusieurs évènements.
On peut la représenter à l’aide de la signature suivante:
(State, Command) => List[Event]
Les évènements produits sont ensuite stockés dans « l’event store ».
Ceci fait, on déclenche la fonction d’évolution pour chacun d’entre eux.
La fonction d’évolution
La fonction d’évolution a pour objectif de faire muter l’état courant de l’application.
Elle ne contient aucune logique métier et c’est très important qu’il en soit ainsi.
C’est une fonction « bête et méchante » !
A partir de l’état courant et d’un évènement, elle produit un nouvel état courant.
On peut la représenter avec la signature suivante:
(State, Event) => State
L’action
Jusqu’à maintenant, nous avons vu que la réception d’une commande activait la fonction de décision.
Cette dernière produit une liste d’évènements qui sont stockés puis, pour chacun d’entre eux, la fonction d’évolution est appelée afin de muter l’état courant.
Il s’agit en fait de la première boucle de notre système d’Event Sourcing. En effet, la mutation de l’état courant consécutive à l’application d’évènements peut donner lieu à une réaction de notre système. Cette dernière se matérialise sous la forme d’une action qui n’est autre qu’une commande interne.
Une action représente une réaction initiée par votre application (on parle de système réactif). En ce sens, elle est exprimée à l’impératif pour marquer cette intention.
Ex: ForwardPrice
Elle contient tous les paramètres nécessaires à sa description (elle est autosuffisante) et déclenche un second tour de boucle, identique au premier.
L’effet de bord
Lorsque les deux tours de boucle sont terminés et qu’en conséquence, votre application se trouve dans un état stable, l’ensemble des évènements produits sont transmis vers l’extérieur. On parle alors d’effets de bord. Ces derniers caractérisent à l’extérieur de votre système ce qui s’est produit à l’intérieur.
Couramment, les évènements produits sont émis sur un bus où souscrivent les systèmes en charge de l’envoi de messages vers l’extérieur.
C’est également à partir de ce bus d’évènements que l’on pourra construire les vues de lecture d’un système CQRS.
Points d’attention
Ne confondez pas Event Sourcing et Command Sourcing
L’Event Sourcing apporte des capacités de reprise de données par le re-jeu des évènements stockés.
Selon la littérature, il en existe deux variantes selon que vous sauvegardiez vos évènements (Event Sourcing) ou vos commandes (Command Sourcing).
Or, les conséquences de ce choix sont fortes et rarement maîtrisées; Jeremie Chassaing avait d’ailleurs publié un article en ce sens dès 2013.
Re-jeu dans le cadre de l’Event Sourcing
Si vous décidez de sauvegarder vos évènements, le re-jeu ne fera intervenir que la fonction d’évolution :
Ainsi, vous n’aurez pas à rejouer la fonction de décision (donc vos règles métiers) et aucun effet de bord ne sera produit vers l’extérieur.
Il suffit de partir de l’état initial et d’appliquer la fonction d’évolution à chacun des évènements sauvegardés pour revenir dans l’état courant.
Re-jeu dans le cadre du Command Sourcing
Si vous décidez de sauvegarder vos commandes, le re-jeu fera intervenir l’ensemble de votre système.
C’est-à-dire que la fonction de décision sera rejouée et que des effets de bords seront produits vers l’extérieur.
Cela pose plusieurs problèmes majeurs :
- Comment vos partenaires font-ils pour distinguer un évènement classique d’un évènement de re-jeu ?
- Que se passe-t-il si, entre la commande initiale et le re-jeu, vos règles de gestion ont évolué ?
Le premier point nécessite d’avoir des partenaires idempotents, c’est-à-dire qui sont capables de recevoir une ou plusieurs fois le même message. Ce qui, sur le terrain, est rarement le cas.
Le second point est plus impactant encore. Voyez l’exemple ci-après :
La commande initiale, reçue en 02/2016, a produit un évènement à priori immuable avec une marge de 0,10€.
Lors du re-jeu de cette commande en 07/2016, la règle de gestion a changé et l’évènement produit n’est plus le même: il a dorénavant une marge de 0,20€.
En modifiant malencontreusement le passé de la sorte, on perd toute capacité d’audit ou d’analyse/debug car l’on n’est plus à même d’assurer que les évènements se sont réellement produits. Des solutions existent, comme celles mentionnées par Martin Fowler dans son article « Event Sourcing », mais leur impact sur la maintenabilité est non-négligeable.
De ce fait, veillez à sauvegarder vos évènements et non vos commandes à moins d’avoir une excellente raison de ne pas le faire.
Nommer un évènement n’est pas chose aisée
Nous l’avons vu au début de cet article, les évènements de votre système sont par nature « métier ». Ils doivent donc avoir du sens pour vos utilisateurs et être exprimés dans un langage partagé par tous (métier, développeurs, testeurs). De plus, nous souhaitons les exprimer à la voie passive pour marquer le fait qu’ils se sont produits dans le passé et sont en ce sens immuables. C’est d’autant plus vrai que ces évènements ont vocation à être publiés et visibles, notamment si vous associez votre architecture à un système CQRS. Publier des évènements laissant place au doute est dangereux et à éviter. Il faut être le plus explicite possible et c’est loin d’être évident.
Prenons par exemple l’évènement RequestForwarded
. Celui-ci laisse place à de nombreuses interprétations. De quelle requête parle-t’on? A qui a-t’elle été envoyée ?
Ainsi, il faut préférer des noms tels que PriceForwaredToClient
qui lèvent toute ambiguïté.
De même, évitez les verbes trop génériques, principalement ceux utilisés dans l’acronyme CRUD (Create Read Update Delete).
Préférez les verbes portant la sémantique de votre métier.
Ex: ClientCreated
vs ClientRegistered
.
Si le sujet vous intéresse, nous vous conseillons cet excellent article de Miroslaw Praglowki, « The anatomy of Domain Event ».
Conclusion: vous pouvez (devez ?) faire sans framework
Nous l’avons vu ensemble, les concepts derrière l’Event Sourcing sont relativement simples.
En ce sens, implémenter un tel système peut se faire sans framework ni bibliothèque.
Cela vous permettra de vous confronter au plus tôt aux choix fondateurs d’une telle architecture tout en obtenant rapidement des résultats exploitables.
Si la performance est un sujet d’importance pour vous, vous serez bien plus à même d’optimiser votre implémentation.
N’hésitez pas à vous reporter à l’excellent atelier Mixter pour vous accompagner dans cette tâche.
Vos questions sont également les bienvenues ; n’hésitez pas à poster un commentaire pour nous les poser.