Event Sourcing et CQRS, le futur de la gestion de nos données ?
Event Sourcing et CQRS révolutionnent la gestion des données en séparant lecture et écriture, tout en plaçant les événements au cœur des systèmes. Une approche moderne pour des applications performantes, évolutives et résilientes face aux défis actuels.
Cet article a pour but de démontrer un exemple d'implémentation de l'Event Sourcing en TypeScript sans pour autant être une référence exhaustive.
Event Sourcing
Event Sourcing est une stratégie de stockage des données qui se base sur un historique d'événements plutôt que le stockage d'un état final comme c'est le cas pour une base de données SQL.
Néanmoins, si l'état d'une application se résume à des centaines, des milliers voir des millions d'événements, comment représenter cela sous la forme d'un modèle de données comme on le connaît ?
Si vous avez été familier avec la programmation fonctionnelle, vous connaissez déjà la réponse : utiliser un reducer.
Programmation Fonctionnelle
Pourquoi est-ce que la programmation fonctionnelle et l'Event Sourcing font bon ménage ensemble ?
La programmation fonctionnelle est un ensemble de concept permettant d'adopter un paradigme de programmation qui met en avant la sécurité des données et du code avant toute chose, et permet d'activer un certain nombre de choses intéressantes pour nous au travers du code.
Par exemple, il vous est peut-être arrivé de vouloir mettre en cache le résultat d'une requête, avant de vous apercevoir que cela n'était pas une mince affaire lorsque l'on est amené à avoir plusieurs mutations dans notre code qui pourrait invalider notre cache.
C'est quelque chose qui n'est tout simplement pas possible en programmation fonctionnelle grâce à l'un de ses principes qui est l'immutabilité et qui nous explique qu'une fois une donnée créée, elle ne peut plus être modifiée, et il est nécessaire d'en créer une nouvelle pour avoir un semblant de modification, plutôt que de modifier une donnée.
Donc plutôt que d'avoir ce code.
let email = "old@domain.com";
email = "new@domain.com";
Nous pourrions avoir à la place ce code.
const oldEmail = "old@domain.com";
const newEmail = "new@domain.com";
Désormais, si nous prenons l'état final de ces deux programmes, et que nous nous posons la question suivante : combien de modification d'adresse email y a-t-il eu sur ce compte ?
Si nous avions accès à l'état final de la mémoire-vive de ce programme, ce qu'on appelle généralement un Memory Snapshot, il n'y a que dans le second code que nous pourrions voir en mémoire deux valeurs, tandis que dans le code précédent, où nous retrouvons notre mutation, il n'est plus possible d'avoir cette information puisque la variable a été écrasée en mémoire-vive.
Nous pourrions aller encore plus loin, et considérer qu'à la place de simple variable, nous aurions plutôt à la place des événements.
const userCreatedEvent = {
type: "USER_CREATED",
data: {
id: "abc",
email: "old@domain.com",
},
};
const emailUpdatedEvent = {
type: "EMAIL_UPDATED",
data: {
id: "abc",
email: "new@domain.com",
},
};
Dans ce cas, il est bien plus clair pour un humain de voir ce qu'il s'est passé, mais pour une application souhaitant consommer ces données, ou bien avoir un état à afficher, comme une application Web par exemple, il serait bien pénible d'avoir à manipuler ces données directement.
Pour cela, nous pourrions conserver, dans un coin séparer, un état transitif : c'est-à-dire que notre état pourra évoluer, comme une variable, tandis que notre historique d'événement lui ne pourra que grandir avec le temps, mais aucune modification ni suppression ne sera faite dans cet historique-là.
type UserCreatedEvent = {
type: "USER_CREATED",
data: {
id: string,
email: string,
},
};
type EmailUpdatedEvent = {
type: "EMAIL_UPDATED",
data: {
id: string,
email: string,
},
};
type ApplicationEvent =
| UserCreatedEvent
| EmailUpdatedEvent;
type User = {
id: string,
email: string,
}
function applyEvent(user: User | null, event: ApplicationEvent): User | null {
switch (event.type) {
case "USER_CREATED":
return {
email: event.data.email,
id: event.data.id
}
case "EMAIL_UPDATED":
if (user === null) {
return null;
}
return {
...user,
email: event.data.email
}
}
}
const events: ApplicationEvent[] = [];
let user: User | null = null
const userCreatedEvent: UserCreatedEvent = {
type: "USER_CREATED",
data: {
id: "abc",
email: "old@domain.com",
},
};
events.push(userCreatedEvent);
user = applyEvent(user, userCreatedEvent);
const emailUpdatedEvent: EmailUpdatedEvent = {
type: "EMAIL_UPDATED",
data: {
id: "abc",
email: "new@domain.com",
},
};
events.push(emailUpdatedEvent);
user = applyEvent(user, emailUpdatedEvent);
Nous sommes bien entendu allés beaucoup plus loin dans cet exemple, qui mérite une petite explication.
Nous commençons par définir les événements possibles qui peuvent être déclenchés dans notre application.
Puis, nous avons créé une fonction nous permettant de transformer un événement en état. Pour les personnes qui sont habituées à la programmation fonctionnelle, ce n'est ni plus ni moins qu'un reducer, ou un leftFold.
Enfin, nous avons simplement créé quelques événements, et au fur et à mesure, nous avons ajouté nos événements dans un tableau, tout en créant et modifiant au fur et à mesure notre état.
Si nous avions fait tout cela avec des mutations, nous aurions eu quelque chose plutôt comme cela.
type User = {
id: string,
email: string,
}
let user: User | null = null
user = {
email: "old@domain.com",
id: "abc"
}
user = {
email: "new@domain.com",
id: "abc"
}
C'est plus court, plus simple, plus proche de ce que nous avons l'habitude d'utiliser, donc c'est meilleur, non ?
Tout dépend de votre point de vue.
Si la performance est votre priorité, et que vous n'avez pas de question métier, qu'il n'est pas nécessaire de pouvoir revenir en arrière, ni d'auditer un historique de ce qu'il s'est passé, dans ce cas-là, c'est ce code que vous utiliserez, et il y a de forte chance que ce soit via une base de données traditionnelle comme MySQL ou MongoDB que vous stockerez vos informations, et uniquement l'état final de votre application.
Néanmoins, cela signifie aussi qu'il n'est plus possible de se poser des questions intéressantes, par exemple :
- Quel était le nombre d'utilisateurs entre le 3 mars et le 7 juillet ?
- Combien de fois en moyenne est-ce que les utilisateurs mettent à jour leurs informations ?
- Est-ce qu'un utilisateur a déjà essayé de supprimer son compte ?
Toutes ces informations, nous les perdons malheureusement si nous utilisons une base de données qui n'implémente pas du tout l'Event Sourcing, et comme vous le savez, l'information est l'or du monde numérique.
Event Sourcing vs Domaine d'Ingénierie
Éloignons-nous de l'informatique le temps d'un chapitre et intéressons-nous au monde qui nous entoure, pour lequel notre travail est important et implémente les règles métier de ce monde-là.
En comptabilité, lorsqu'une facture est éditée, il n'est légalement plus possible de la modifier, ni de la supprimer.
C'est intéressant, car cela permet, et heureusement, de conserver l'intégrité des données comptables d'une entreprise, il est alors bien plus difficile pour une personne de falsifier des comptes par exemple (même si, malheureusement, il existe d'autres technique et montage financiers permettant de le faire, nous omettrons ces détails gênant de la législation comptable) et permet un audit à tout moment de la vie d'une entreprise sur l'état de sa balance comptable à un instant choisi.
Cela n'est possible que parce qu'une facture est un fait historique, et que chaque facture est stockée de manière séquentielle dans un livre comptable.
Que se passe-t-il si je me trompe sur une facture et que je l'enregistre dans mon bilan comptable ? Est-ce que les comptables ne font jamais d'erreur ? Cela serait trop beau, malheureusement je peux vous garantir que ce n'est pas le cas et ils restent des êtres humains comme vous et moi.
Néanmoins, lorsqu'une erreur survient, et qu'on la détecte trop tard, la législation nous indique qu'il est possible de créer ce qu'on appelle une facture d'avoir.
Une facture d'avoir est une facture qui vient à l'inverse d'une facture précédemment éditée. Donc si par exemple je vous fais une facture d'un produit de 150€, que je me trompe et que je réalise que je vous ai facturé plus que nécessaire, je ne pourrais pas reprendre la facture, la modifier, et vous la restituer de nouveau.
Ce fait restera dans notre bilan comptable, et pour corriger le tir, je ferais donc une facture d'avoir, qui viendra annuler la précédente, ainsi qu'une nouvelle facture avec le bon montant, comme vous êtes sympa, je vous fais la facture à 110€ plutôt que 150€.
Cela ne vous rappelle rien ? Nous en avons parlé précédemment : c'est le principe même de l'Event Sourcing, où nous pourrions tout à fait représenter cela sous la forme d'un programme.
type InvoiceCreated = {
type: "INVOICE_CREATED",
data: {
amount: number,
},
};
type InvoiceCanceled = {
type: "INVOICE_CANCELED",
data: null,
};
type ApplicationEvent =
| InvoiceCreated
| InvoiceCanceled;
type Invoice = {
amount: number
}
let invoice: Invoice | null = null;
function applyEvent(invoice: null | Invoice, event: ApplicationEvent) {
switch (event.type) {
case "INVOICE_CREATED":
return {
amount: event.data.amount,
};
case "INVOICE_CANCELED":
return null;
}
}
invoice = null;
console.log(invoice);
// Aucune facture ici
invoice = applyEvent(invoice, {
type: "INVOICE_CREATED",
data: {
amount: 150,
},
});
console.log(invoice);
// Notre facture : { amount: 150 }
invoice = applyEvent(invoice, {
type: "INVOICE_CANCELED",
data: null
});
console.log(invoice);
// null, car notre facture vient d'être annulée
invoice = applyEvent(invoice, {
type: "INVOICE_CREATED",
data: {
amount: 110,
}
});
console.log(invoice);
// De nouveau notre facture, { amount: 110 }
Comme vous pouvez le voir dans cet exemple, notre client, au travers de notre programme, ne vois qu'une seule facture à la fois (voir aucune), c'est une décision qui a été prise dans cet exemple puisque notre application n'est qu'une projection de notre historique (plus à propos de cela plus tard).
Néanmoins, nous pouvons toujours revenir dans le temps, et afficher l'état de notre application au moment où nous annulons la facture (plus aucune facture, opération blanche pour l'utilisateur qui sera rassuré de savoir que l'erreur a été corrigée), puis quelques instants plus tard, la facture qui s'affiche avec le bon montant.
Bien entendu, du point de vue de cette application, un utilisateur qui aurait un minimum de connaissance juridique pourrait penser que cela n'est pas une application très légale, mais c'est sans oublier que nous avons en réalité l'ensemble de l'historique côté système de stockage, c'est-à-dire qu'il est toujours possible non-seulement de revenir dans le temps, mais surtout d'afficher les données d'une autre façon.
Nous pourrions tout à fait créer une autre version de notre application, mais cette fois-ci à destination de comptable qui auraient un besoin différent d'un utilisateur.
let invoices: Invoice[] = [];
function applyEvent(invoices: Invoice[], event: ApplicationEvent) {
switch (event.type) {
case "INVOICE_CREATED":
return [
...invoices,
{
amount: event.data.amount,
},
];
case "INVOICE_CANCELED":
const previousInvoice = invoices[invoices.length - 1];
return [
...invoices,
{
amount: previousInvoice.amount * -1,
},
];
}
}
console.log(invoices);
// Aucune facture ici
invoices = applyEvent(invoices, {
type: "INVOICE_CREATED",
data: {
amount: 150,
},
});
console.log(invoices);
// 1 facture
invoices = applyEvent(invoices, {
type: "INVOICE_CANCELED",
data: null
});
console.log(invoices);
// 2 factures (dont une facture d'avoir avec un montant négatif)
invoices = applyEvent(invoices, {
type: "INVOICE_CREATED",
data: {
amount: 110,
}
});
console.log(invoices);
// 3 factures, dont la nouvelle facture de 110€
Comme vous pouvez le constater, nous n'avons absolument pas modifié le contenu de nos événements, tout ce que nous avons fait, c'est de modifier la manière dont sont affichées nos données, pour une autre catégorie d'utilisateur qui ont des besoins spécifiques et différents de nos utilisateurs commerciaux.
Pourtant, pour un client, comme pour un comptable, nos données n'ont absolument pas changé, nous avons toujours l'historique de nos trois factures, quel que soit l'utilisateur de nos deux programmes, mais nous avons deux manières de les afficher sous la forme d'un état final.
C'est ce que nous permet de faire l'Event Sourcing : notre base de données principale n'est que l'état de toutes les actions depuis le début de notre activité, stockées sous la forme d'un tas d'événements, tous immuables, et l'état de notre application n'est qu'une projection de tous ces événements.
Pensez à une base de données qui ne stockerait que l'état final d'une application : il serait impossible de stocker une facture, puis de la supprimer, puis d'en créer une nouvelle, et d'espérer que l'application à destination de nos comptables puisse fonctionner comme ils l'entendent.
Bien sûr, cet exemple est contraire à toute la réglementation comptable, quelle que soit la stratégie que vous utilisez, s'il vous plaît ne supprimez jamais de facture !
Des exemples métiers comme ceux-ci, ils en existent en réalité dans quasiment tous les domaines métiers : un avocat fera un amendum à un document législatif, un contrat de travail sera modifié via la création d'un avenant et le contrat original restera tel quel, un chef de projet créera un nouveau cahier des charges avec une version incrémentée en cas de changement.
Tous ces domaines utilisent de l'Event Sourcing ! Nous sommes le seul domaine d'ingénierie qui utilise une base de données, qui stockons uniquement l'état final et pas l'historique de toutes nos actions, et qui espérons pouvoir maintenir et faire évoluer des applications sur le long terme !
J'espère qu'il apparaît clair maintenant que l'Event Sourcing devrait être désormais votre principal source de vérité.
Adieu SQL, MongoDB, Neo4j ?
Si nous lisons cet article, nous pourrions croire que l'idée est de se passer complètement de base de données que nous avons l'habitude d'utiliser comme PostgreSQL, MongoDB ou Cassandra.
C'est faux ! L'Event Sourcing et les bases de données que vous utilisez peuvent très bien fonctionner ensemble, c'est d'ailleurs tout l'intérêt.
L'Event Sourcing n'est qu'une stratégie de stockage des données sous la forme d'un log d'événements, néanmoins comme vous avez pu le voir jusqu'à maintenant, un état est toujours nécessaire car c'est au travers de ce dernier que nous pouvons travailler à créer des interfaces autour de ce dernier, pas en repartant de nos événements.
Vous pourriez d'ailleurs tout à fait utiliser une base de données SQL pour stocker vos événements, et une base de données MongoDB pour stocker vos projections (votre état, transitif ou non), voir l'inverse, voir même stocker tout dans PostgreSQL, ce choix ne fait de sens que selon le contexte de votre application et de vos besoins techniques.
Néanmoins il est important de bien séparer ces deux concepts : vos actions (créer une facture, modifier un utilisateur, envoyer un message) viennent toujours ajouter des événements, qui viendront par la suite mettre à jour votre état (vos tables SQL) qui seront ensuite requêtées par vos applications (Web, mobile, bureau, ...).
Si vous avez compris comment fonctionne un reducer, vous pourriez être tenté de vous dire : adieu SQL, adieu MongoDB, adieu Neo4j, bonjour la programmation fonctionnelle, reducer et mes événements pour pouvoir recréer au besoin (à chaque requête) mon état, nous pourrions ainsi nous libérer du joug de ces systèmes complexes.
Cela est une idée tentante, surtout que c'est une idée qui marche très bien et vous pourriez dès maintenant vous arrêter et créer votre prochaine idée d'application basé sur ce principe, et cela fonctionnerait !
Jusqu'au moment où vous, ou vos clients, vous retrouverez avec des millions, voir des milliards d'événements, sur plusieurs années, et cela deviendrait vite une opération à la fois coûteuse en CPU et en mémoire-vive, jusqu'à ce que vous soyez obligez d'investir dans une meilleur configuration.
Néanmoins il est nécessaire de rester le plus pragmatique possible : les concepts que vous avez appris désormais ne doivent pas être appliqués à la lettre à toutes les sauces pour toutes les applications indépendamment du contexte de ces dernières.
Comme pour tout, il est nécessaire de faire preuve de discernement, et de savoir où poser une frontière entre la théorie et la pratique.
Bien sûr, si vous savez que vous créez une application pour une bijouterie de luxe, pour lequel le nombre de client est bien moins important qu'une application de mise en relation de patient avec des professionnels de santé, vous n'allez pas implémenter l'Event Sourcing de la même façon.
Pour une application pour une bijouterie de luxe, il peut être tout à fait intéressant de commencer par des requêtes qui recalculent votre état en fonction de vos événements passés, car mettre en place une base de données n'est pas un coût négligeable et pour le nombre réduit d'événements, cela pourrait être bien plus coûteux d'utiliser une base de données faite pour servir des millions voir des milliards de requêtes pour une centaine de clients par an.
Néanmoins, pour une application de mise en relation de patients avec des professionnels de santé, il serait bien prétentieux de penser que recalculer un état par rapport à des précédents événements à chaque requête pourrait être plus économe que de stocker au fur et à mesure l'état de notre application, et de faire appel à une base de données pour pouvoir servir nos requêtes.
Tout dois être mesuré pertinemment et avec discernement, c'est ce qui fera de vous un excellent ingénieur informatique.
CQRS : Command & Query Resource Separation
Bien souvent, Event Sourcing et CQRS sont deux concepts qui vont de pairs, justement par rapport à ce que nous avons précédemment pu voir.
Nous avons posé une claire séparation entre les requêtes qui vont venir ajouter des événements (Command) et les requêtes qui vont venir récupérer des données depuis une base de données de projection (Query).
C'est pour cela que nous parlons souvent de CQRS lorsque nous parlons d'Event Sourcing. Comme pour l'Event Sourcing, le CQRS n'est qu'un terme barbare devant un concept très simple qui est de séparer clairement nos requêtes de nos commandes.
Les commandes sont des opérations qui vont venir modifier l'état d'une base de données de stockage bien souvent, c'est notamment à ce moment qu'une base de données de stockage d'événements sera modifié (comprendre, on ajoute un événements, on ne modifie ni supprime jamais d'événements).
Tandis que les requêtes, ou queries, vont venir récupérer une information, pour un client ou un comptable par exemple. C'est à ce moment que nous pourrions choisir, en fonction de la cible, la base de données à requêter par exemple, et nos commandes pourraient tout à fait mettre à jour l'état de ces deux bases de données de projection pour nos deux cibles.
Imaginez une application comme Uber qui aurait besoin d'une base de données Neo4j afin de pouvoir faire des requêtes graphes complexes pour le chef d'entreprise afin de pouvoir prendre des décisions sur la cible réelle de cette application, et de la façon dont il pourrait améliorer son service pour la plus grande catégorie d'utilisateur et de leur relations.
Ou encore, une base de données MongoDB bien taillée pour de la recherche en GeoJSON, afin d'accélérer les requêtes faites par les clients pour chercher le coursier le plus proche de chez eux, et de calculer le chemin le plus rapide pour un livreur vers le domicile d'un client.
Ou encore une base de données SQL pour les clients qui souhaiteraient faire une recherche avancée multi-critères sur les restaurants les mieux notés et les moins cher, avec les contre-indications alimentaires de chacun, requête qui pourraient être optimisée au travers de vue matérialisées par exemple.
Tout cela pourraient très simplement se faire si vous repartez d'un ensemble de log. Pas satisfait par le résultat d'une requête, ou la modélisation SQL adoptée ? Aucun souci, il suffit de tout supprimer et de tout recommencer, c'est très simple, surtout si vous avez compris comment fonctionne notre reducer, il suffira simplement de réfléchir au prochain modèle de données qui satisfera nos utilisateurs.
L'intérêt d'avoir séparé ces deux parties pourra d'être, par exemple, de mettre en place du cache sur nos requêtes les plus coûteuses, créer plus de bases de données proches en terme de géolocalisation de nos utilisateurs les plus nombreux dans différents pays du monde, tout cela car l'état de notre application n'est qu'une fonction pure qui est calculée par rapport à nos événements, qui sont eux immuables, il est donc très simple de mettre en cache le résultat d'une telle fonction et c'est ce qui est fait en programmation fonctionnelle pour optimiser certains algorithmes en utilisant de la mémoisation entre autres techniques d'optimisation, chose qui n'est pas possible si une base de données est vouée à changer de schéma par exemple.
Migration, complexité et autres reliques du passé
Un autre effet bénéfique à l'Event Sourcing est le fait qu'il n'est absolument plus nécessaire d'avoir des migrations !
Oui vous avez bien lu, et si vous avez assez d'expérience avec les migrations, vous savez à quelle point il peut être dangereux d'appuyer sur ce fameux bouton de déploiement et d'espérer qu'aucune corruption de données ou de changements de schéma ne viennent engendrer une perte de données.
Si vous avez déjà eu ce sentiment et cette boule au ventre, c'est que vous êtes sur le bon chemin, et que vous avez compris que la données est ce qu'il y a de plus important dans une application informatique comme nous en avions précédemment discutés.
Imaginons que nous avons ce modèle de données SQL suivant.
CREATE TABLE users (
id CHAR(60) PRIMARY KEY,
email VARCHAR(50) UNIQUE NON NULL,
firstname VARCHAR(30) NON NULL,
lastname VARCHAR(30) NON NULL
);
Plus tard, on vous annonce qu'en réalité, le prénom et le nom de famille ne sont plus nécessaire après 1 an d'exploitation de l'activité. Donc naturellement, vous créez le fichier SQL permettant de faire la migration suivante.
ALTER TABLE users DROP COLUMN firstname;
ALTER TABLE users DROP COLUMN lastname;
Bien souvent d'ailleurs, vous auriez plutôt écrit du code directement dans votre langage de programmation, notamment en PHP si vous avez utilisé Laravel par exemple. Souvent, une migration est reversible, c'est-à-dire qu'elle contient aussi un autre code qui va permettre de revenir en arrière.
Comment revenir en arrière après avoir supprimé les prénoms et nom de famille de plusieurs dizaines de milliers d'utilisateurs ? Quelle serait la requête SQL pour pouvoir faire cela ?
La réponse : aucune. Vous venez tout simplement de perdre toutes les données de vos utilisateurs, car vous n'avez pas d'historique de logs, et que vous n'avez stocké que l'état final de votre application. C'est littéralement une migration irreversible. Ce simple fait devrait vous donner des frissons et vous faire réaliser à quel point l'Event Sourcing peut vous apporter en terme d'intégrité et de sécurité de données, car cela peut arriver à tout le monde de faire des erreurs, de lancer des migrations qui ne devaient pas être lancées sur une base de données de production par exemple.
Avec de l'Event Sourcing, ces cas de figures n'existent pas car il ne sont tout simplement pas possible : il suffit de configurer une base de données SQL (sur PostgreSQL par exemple) pour empêcher les suppressions, et n'autoriser que les écritures et les ajouts pour pouvoir rendre votre base de données résiliente aux erreurs humaines et stocker vos événements en toute sérénité. Et quasiment toutes les bases de données modernes permettent un système de gestion des droits avancé permettant de sécuriser une base de données.
Pour le reste, vos projections, vous pouvez faire ce que vous voulez !
De plus, la complexité liée aux migrations n'existe plus du tout : besoin de supprimer une colonne ? Créez simplement une seconde version de votre événement !
type UserCreatedEventV1 = {
type: "USER_CREATED",
version: 1,
data: {
email: string,
firstname: string,
lastname: string
}
}
type UserCreatedEventV2 = {
type: "USER_CREATED",
version: 2,
data: {
email: string
}
}
Félicitations, vous venez de créer votre migration. Ce n'est ni plus ni moins qu'un énième événements qui sera ajouté à votre liste d'événements.
Comme une base de données en Event Sourcing est immuable, tous les anciens événements qui existaient auparavant existent et existeront toujours.
Et du point de vue d'une vue SQL, il suffira tout simplement de supprimer la base de données de lecture, de la recréer, et d'ignorer le prénom et nom de famille des anciens événements.
Vous conservez toujours ces données au cas où, pour les nouveaux utilisateurs qui seront créé, puisqu'ils n'ont pas été créé avec la possibilité d'avoir un prénom et un nom de famille, cela ne change rien, et vous pouvez toujours revenir en arrière en ajoutant de nouveau un prénom et nom de famille : les anciens utilisateurs pourront avoir un nom de famille et prénom générique, ou bien vide, vous pouvez même vérifier cela au moment de leur connexion et leur afficher un bandeau de changement rapide de leurs informations avant de continuer, et les anciens utilisateurs auront toujours leurs anciennes informations, chose impossible si vous aviez utilisé un état final uniquement.
Sécurité
Évidemment, un des nombreux avantages à l'Event Sourcing et au CQRS et le fait que, puisque nous avons la possibilité de créer N représentation de notre état pour N catégorie d'utilisateur, il est alors tout à fait possible de supprimer des données qui ne seraient pas adaptées, voir sensible très facilement de cette façon, sans risquer de perdre de la données.
C'est notamment très intéressant si jamais nous faisons une erreur et que nous envoyons trop d'informations que nécessaire, par exemple, une liste d'utilisateur qui contiendrait le mot de passe haché, même si ce dernier est haché, ce n'est pas forcément la meilleure des choses à envoyer à une liste qui pourraient être publique comme une liste d'utilisateur d'un système de chat.
C'est un effet de bord à l'Event Sourcing et CQRS qui est fort appréciable, notamment parce que la sécurité est un point essentiel de toute application moderne.
Conclusion
En conclusion, nous dirons que l'Event Sourcing et le CQRS ne sont pas le futur de la gestion de nos données.
Bizarre, non ? Après tout ce que l'on vient d'expliquer, on pourrait croire que ce serait la marche à suivre pour nos prochaines applications dorénavant, et vous auriez raison.
Néanmoins, l'Event Sourcing existe depuis bien plus longtemps même que depuis l'existence et la création de nos bases de données favorites.
De plus, vous utilisez forcément des programmes qui utilisent l'Event Sourcing aujourd'hui et qui garantissent d'avoir des systèmes fiables et pérenne dans le temps.
Git par exemple, vous offre la possibilité de revenir dans le temps, à un état précédent du développement de votre application car il stocke l'historique complet de tous les changement que vous avez enregistré.
Toutes les bases de données utilisent en interne un système de log, comme SQLite avec le WAL (Write Ahead Log). Ce système permet à une base de données de savoir à quelle étape est-ce qu'elle en est, pour pouvoir savoir quel est l'état de la base de données à certains points dans le temps, et de pouvoir revenir à un état précédent par exemple, notamment lorsqu'on utilise des transactions (BEGIN, COMMIT, ROLLBACK).
Google Docs, qui utilise un système similaire afin de vous proposer l'ensemble des modifications, et de pouvoir revenir à une précédente version après une coupure catastrophique de courant entrainant une perte de données.
L'Event Sourcing a toujours existé, et de grandes entreprises utilisent cette stratégie pour créer des applications modernes et maintenables dans le temps.
Aurions-nous dû d'abord commencé par l'Event Sourcing, avant d'apprendre à manipuler une base de données ? Qu'en pensez-vous ? C'est une question auquel je vous laisserais trouver une réponse intéressante et réflechie.