https://a.storyblok.com/f/270183/1368x665/a1e36438ef/25jul_dev_blog_event-sourcing.png

Dois-je faire appel à Event Source ?

Publié le July 17, 2025

Temps de lecture : 8 minutes

Si les années 2020 ont montré quelque chose dans le domaine de la technologie, le mot "blockchain" y figure dans une myriade d'interprétations positives et négatives. Si nous prenons les aspects "bons" ou "utiles" d'une blockchain, ils sont souvent liés à l'exploitation des avantages de sa nature immuable. L'histoire est l'histoire.

Vous avez une idée et vous vous dites que la blockchain serait parfaite pour cela, mais vous devez aussi reconnaître l'éléphant dans la pièce : le web3, les blockchains et la technologie cryptographique sont des technologies difficiles à intégrer. Si vous venez d'une approche traditionnelle de l'ingénierie logicielle, bonne nouvelle ! Il existe un modèle de conception de données qui existe depuis un certain temps (en particulier dans des domaines tels que la finance) qui a une approche similaire pour retracer l'état historique des données. Dans cet article, nous allons explorer les points suivants L'Event Sourcing.

Les origines de l'Event Sourcing

Image showing generic line chart data being mappedAll Together Now: Data is the new OilLe concept d'Event Sourcing est né de la nécessité d'enregistrer les données transactionnelles et de déterminer les transformations qu'elles subissent au fil du temps. Les institutions telles que les banques auraient besoin de pouvoir voir un "instantané" de ce à quoi ressemblait une entité à un moment précis. En utilisant le concept de conception pilotée par les domaines (DDD), la naissance plus formelle de l'Event Sourcing s'est produite avec le lancement de Command Query Responsibility Segregation (séparation des responsabilités des commandes et des requêtes) (ou CQRS), couramment utilisée dans les environnements Microsoft. Cette ségrégation se concentre non pas sur les données, mais sur les actions qui se produisent lorsque leur état change. Ces actions sont enregistrées en tant qu'entités immuables, et c'est ce modèle qui définit l'Event Sourcing.

Une application typique en finance

Photo of paperwork showing generic stock market press reportingIt's a Bull Market, but how do you audit it?Prenons, par exemple, quelque chose de simple comme le solde d'un Account. Dans une configuration traditionnelle, vous auriez une base de données relationnelle et une application extrayant des données de ce compte à l'aide d'une instruction SQL préparée. Le chiffre du solde pourrait être obtenu soit si les transactions conservent le solde du compte dans l'état de chaque transaction, soit si l'application tente de tout agréger. C'est là que l'Event Sourcing entre en jeu : que faire si vous avez des millions de lignes et que vous voulez voir l'état d'un solde à un moment précis dans le passé ? Vous devez espérer que les données transactionnelles sont toutes correctes, et qu'une migration en aval n'a pas eu lieu, ou qu'une panne ne s'est pas produite au moment précis où l'on tente de reporter un solde total dans un enregistrement de transaction.

Avec l'Event Sourcing, vous "rejouez l'histoire" car les données conservées concernent les changements d'état des données. Pour appliquer cette méthode, vous devez donc créer des modèles d'événements personnalisés, nommés de manière appropriée, comme par exemple

class AccountCreatedEvent extends Event

class AccountDepositEvent extends Event

class AccountWithdrawEvent extends Event

Cette série d'événements est appelée un flux. Pour obtenir le solde, on utilise un agrégateur, appelé a projecteur. Une classe de projecteur prendrait un argument, puis lirait les événements (interrogés précédemment par une clé primaire telle que l'ID du compte) qui lui sont transmis pour déterminer l'état :

class AccountBalanceProjector {

    public static int projectBalance(List<Event> events) {

        int balance = 0;
        boolean accountCreated = false;

        for (Event event : events) {
            if (event instanceof AccountCreatedEvent) {
                balance = ((AccountCreatedEvent) event).initialBalance;
                accountCreated = true;
            } else if (accountCreated && event instanceof AccountDepositEvent) {
                balance += ((AccountDepositEvent) event).amount;
            } else if (accountCreated && event instanceof AccountWithdrawEvent) {
                balance -= ((AccountWithdrawEvent) event).amount;
            }
        }
        return balance;
    }
}

Et voilà. Vous auriez une méthode dans votre ORM (Object-Relational Mapper) qui récupèrerait tous les événements d'un accountIdpuis transmettrait ces événements à AccountBalanceProjector.projectBalance(events)et vous obtenez votre solde. Il ne s'agit pas d'extraire le solde qui a été préconstitué quelque part, mais de le recréer.

Utilisation de l'Event Sourcing avec Vonage Data

Graphic design image showing developers hard at workControlling Data Can Be TrickyPrenons un exemple en utilisant les données générées lors de l'utilisation de Vonage. Nous allons nous intéresser à l'API Voice, qui génère un webhook d'état d'événement à chaque fois qu'une action se produit pendant la durée d'un appel vocal. D'après la documentation sur les webhooks de l'API Voiceles états suivants peuvent être donnés :

Si j'introduis ces états dans l'IA et que je lui demande de générer mes classes, je peux obtenir un petit saupoudrage magique automatisé pour générer ma liste d'événements :

public class CallStarted extends BaseVoiceEvent {
    public CallStarted(String callId) { super(callId); }

    @Override public String getName() { return "started"; }
}

public class CallRinging extends BaseVoiceEvent {
    public CallRinging(String callId) { super(callId); }

    @Override public String getName() { return "ringing"; }
}

public class CallAnswered extends BaseVoiceEvent {
    private String answerType;

    public CallAnswered(String callId, String answerType) {
        super(callId);

        this.answerType = answerType;
    }

    @Override public String getName() { return "answered"; }

    @Override public Map<String, Object> toMap() {
        Map<String, Object> map = super.toMap();
        map.put("answer_type", answerType);
        return map;
    }
}

public class CallBusy extends BaseVoiceEvent {
    public CallBusy(String callId) { super(callId); }

    @Override public String getName() { return "busy"; }
}

public class CallCancelled extends BaseVoiceEvent {
    public CallCancelled(String callId) { super(callId); }

    @Override public String getName() { return "cancelled"; }
}

public class CallUnanswered extends BaseVoiceEvent {
    public CallUnanswered(String callId) { super(callId); }

    @Override public String getName() { return "unanswered"; }
}

public class CallDisconnected extends BaseVoiceEvent {
    public CallDisconnected(String callId) { super(callId); }

    @Override public String getName() { return "disconnected"; }
}

public class CallRejected extends BaseVoiceEvent {
    public CallRejected(String callId) { super(callId); }

    @Override public String getName() { return "rejected"; }
}

public class CallFailed extends BaseVoiceEvent {
    public CallFailed(String callId) { super(callId); }

    @Override public String getName() { return "failed"; }
}

public class CallHumanMachineDetected extends BaseVoiceEvent {
    private String detectionResult;

    public CallHumanMachineDetected(String callId, String detectionResult) {
        super(callId);

        this.detectionResult = detectionResult;
    }

    @Override public String getName() { return "human_machine"; }

    @Override public Map<String, Object> toMap() {
        Map<String, Object> map = super.toMap();
        map.put("detection_result", detectionResult);

        return map;
    }
}

public class CallTimeout extends BaseVoiceEvent {
    public CallTimeout(String callId) { super(callId); }

    @Override public String getName() { return "timeout"; }
}

public class CallCompleted extends BaseVoiceEvent {
    public CallCompleted(String callId) { super(callId); }

    @Override public String getName() { return "completed"; }
}

// etc etc, tronqué pour des raisons de longueur

Vous remarquerez que ces événements s'étendent à partir d'une classe de base, que nous définissons comme abstraite :

import java.time.Instant;

import java.util.HashMap;

import java.util.Map;

public abstract class BaseVoiceEvent {
    protected String callId;
    protected Instant timestamp;

    public BaseVoiceEvent(String callId) {
        this.callId = callId;

        this.timestamp = Instant.now();
    }

    public BaseVoiceEvent(String callId, Instant timestamp) {
        this.callId = callId;

        this.timestamp = timestamp;
    }

    public abstract String getName();

    public Map<String, Object> toMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("event", getName());
        map.put("call_id", callId);
        map.put("timestamp", timestamp.toString());

        return map;
    }
}

Vous disposez à présent de la structure initiale d'une architecture de recherche d'événements. Les étapes finales consisteraient à avoir des contrôleurs pour gérer la création d'événements à un point de terminaison HTTP de votre application, puis un projecteur d'état d'appel. Ce projecteur, similaire à l'exemple précédent, serait capable de prendre un flux d'événements, de prendre un horodatage optionnel, puis de calculer le statut correct du callID en fonction de l'horodatage que vous avez donné (ou du plus récent par défaut).

Quels sont les avantages de l'approvisionnement en événements ?

  • L'architecture pilotée par les événements peut être remaniée. Le système étant découplé des données d'état qui lui parviennent, vous pouvez réexécuter vos nouveaux contrôleurs avec une nouvelle logique (par exemple, pour gérer un nouveau type d'événement) et reconstruire vos flux de données pour la projection.

  • Pistes d'audit complètes indépendamment du tempsLa sécurité des données : cet aspect est très important pour les secteurs très réglementés, comme nous l'avons vu dans des domaines tels que la finance et les soins de santé.

  • Séparation des lectures et des écritures : En utilisant le CQRS susmentionné, la façon dont les données sont accédées et écrites est divisée. Lors de la mise au point d'une application nécessitant une entrée/sortie importante de données, vous pouvez dimensionner des aspects distincts de votre architecture en conséquence.

Quels sont les inconvénients de l'approvisionnement en événements ?

Je ne pourrais pas me considérer comme un ingénieur sensé si je n'indiquais pas quels sont les inconvénients, et ils sont assez importants

  • Compatibilité ascendante : J'ai mentionné la rapidité avec laquelle il est possible d'introduire de nouvelles modifications dans vos modèles et de réexécuter vos gestionnaires d'événements, mais je n'ai pas parlé du moment où quelque chose devient obsolète. Il faut réfléchir à la gestion des événements, au versionnage sémantique et aux comportements, ce qui peut conduire à des collections considérables d'anciennes données qui doivent être stockées et à des procédures ETL complexes

  • Complexité du système : C'est le plus grand obstacle à surmonter. J'ai travaillé par le passé sur des systèmes événementiels qui étaient en train de passer à l'échelle, et la plus grande partie de cette complexité est similaire à celle des microservices. Si vous n'avez pas architecturé votre système d'événements correctement dès le départ, alors je peux vous assurer qu'un enum changeant ne demande qu'à faire trébucher tout votre système

  • Débogage : L'une des plus grandes frustrations que j'ai trouvées dans la conception de logiciels est l'absence d'outils de débogage appropriés. J'ai souvent parlé de ma consternation de ne pas avoir xdebug de Derick Rethan dans une pile PHP ; je ne vois tout simplement pas comment quelqu'un peut coder sans lui, plutôt que de tuer l'exécution et de vider des tableaux ou des chaînes de caractères individuels. De même, le débogage des microservices, ce qui m'amène à parler des environnements d'Event Sourcing : pour déboguer les flux avec précision, vous aurez probablement besoin d'écrire des outils personnalisés supplémentaires au-dessus de votre système pour les cas où les choses tournent mal

  • Stockage : Cela peut paraître un peu évident, mais au lieu d'avoir une seule source de vérité (dans notre exemple, il s'agissait de stocker les Webhooks de l'API Voice de Vonage), vous avez maintenant plusieurs tables supplémentaires qui contiennent beaucoup de données supplémentaires créées en plus des données sources.

Le faire à votre façon

D'après ce que nous avons vu, la structure vanille de ce processus est assez complexe. La bonne nouvelle, c'est qu'il existe des bibliothèques pour nous aider à l'implémenter. Voici mes recommandations pour chaque langage de base :

Conclusion

Comme pour chaque nouveau modèle d'ingénierie qui émerge, je suis souvent cynique quant à l'utilité de ces choses dans la pratique. J'ai vu des discussions sur l'architecture hexagonale, et les applications frontales React à motif d'héritage inverse, et je ne peux tout simplement pas comprendre pourquoi les développeurs voudraient voudraient ce niveau de complexité. Cependant, dans le cas de l'Event Sourcing, je pense que la situation est très différente. Oui, c'est difficile à mettre en œuvre et à architecturer, mais comme pour tout, quel est votre cas d'utilisation ? Si vous écrivez dans un marché vertical qui exige des niveaux élevés d'examen des données, alors le fait de mettre en place l'Event Sourcing dès le départ épargnera à votre entreprise des maux de tête potentiellement importants à l'avenir.

Vous avez une question ou souhaitez partager ce que vous construisez ?

Restez connecté et tenez-vous au courant des dernières nouvelles, astuces et événements concernant les développeurs.

Partager:

https://a.storyblok.com/f/270183/400x385/12b3020c69/james-seconde.png
James SecondeDéveloppeur PHP senior Advocate

Acteur de formation avec une thèse sur la comédie, je suis venu au développement PHP par le biais de la scène des rencontres. Vous pouvez me trouver en train de parler et d'écrire sur la technologie, ou de jouer/acheter des disques bizarres de ma collection de vinyles.