Software Architecture

Maîtriser les événements de domaine : Nommage, charges utiles et découplage en DDD

Le Domain-Driven Design (DDD) est souvent salué pour sa clarté stratégique, mais il peut échouer si l'implémentation tactique est négligée. L'un des composants les plus critiques, mais souvent mal gérés, est l'événement de domaine. Bien implémentés, les événements de domaine permettent à différents contextes délimités de réagir aux changements sans couplage fort. Mal faits, ils créent des dépendances cachées et des systèmes fragiles.

Dans cet article, nous allons analyser les trois piliers d'une conception efficace des événements de domaine : les conventions de nommage, la structure de la charge utile et les stratégies de découplage.

L'art de nommer les événements

Les noms sont votre API. En DDD, les noms d'événements doivent clairement communiquer ce qui s'est passé et quand. Une erreur courante consiste à utiliser des verbes à l'impératif ou des noms ambigus.

Meilleures pratiques :

  • Utilisez le passé : Les événements décrivent des choses qui se sont déjà produites. CommandePassée, et non PasserCommande.
  • Soyez précis : PaiementÉchoué est mieux que ErreurPaiement. La précision réduit l'interprétation pour les abonnés.
  • Incluez la racine d'agrégat : Préfixez ou intégrez le nom de l'agrégat si le contexte n'est pas évident. FactureGénérée vs. FactureApprouvée.

Évitez les noms génériques comme EntitéMiseÀJour. Cela force les consommateurs à inspecter la charge utile pour comprendre l'importance du changement, ce qui viole le principe d'encapsulation.

Structure de la charge utile : Données minimales viables

Un anti-modèle courant consiste à déverser l'état complet de l'entité dans la charge utile de l'événement. Cela crée deux problèmes :

  1. Fuite d'informations : Les abonnés reçoivent des données dont ils n'ont pas besoin, exposant les détails internes de l'agrégat.
  2. Fragilité : Si vous modifiez la structure de l'entité, vous cassez tous les consommateurs, même ceux qui ne s'intéressaient qu'à un seul champ.

La règle de la "Charge utile minimale viable"

Incluez uniquement les données nécessaires pour qu'un abonné puisse agir. Si un abonné en a besoin davantage, il devrait interroger son propre référentiel ou effectuer un appel API séparé.

// ❌ Mauvais : Déverse tout
public class CommandeCrééeEvent {
    public Commande Commande { get; set; } // Contient 50 champs, la plupart non pertinents
}

// ✅ Bon : Seulement ce qui est nécessaire
public class CommandeCrééeEvent {
    public Guid IdCommande { get; }
    public Guid IdClient { get; }
    public DateTime CrééeLeUtc { get; }
    public decimal MontantTotal { get; }
    public Devise Code { get; }
}

Remarquez comment CommandeCrééeEvent n'inclut que les champs nécessaires pour que les systèmes en aval (par ex., Inventaire, Facturation) réagissent. Le Service de Livraison n'a peut-être besoin que de IdCommande et de IdClient pour rechercher l'adresse plus tard. Il n'a pas besoin du MontantTotal.

Découplage : Le but ultime

L'objectif principal des événements de domaine est le faible couplage. Si votre éditeur sait qui sont les abonnés, vous n'avez pas découplé. Vous avez juste ajouté une couche d'indirection.

Stratégies pour un vrai découplage :

  1. Publier via une abstraction : La racine d'agrégat ne devrait pas appeler directement les services. Au lieu de cela, elle expose des événements, et un médiateur ou un bus d'événements les publie.
    public class Commande {
        private readonly List _evenements = new List();
    
        public void Passer() {
            if (Statut != Statut.EnAttente) throw new ExceptionDomaine("Commande non en attente");
            Statut = Statut.Passée;
            _evenements.Add(new CommandePasséeEvent(IdCommande, IdClient, MontantTotal, DateTime.UtcNow));
        }
    
        public List ObtenirEvenementsNonEngagés() => _evenements;
    }
  2. Séparer les événements d'intégration des événements de domaine :
    • Événements de domaine : Utilisés au sein du même contexte délimité ou pour la cohérence interne. Ils peuvent être synchrones ou asynchrones mais sont étroitement couplés à la logique du domaine.
    • Événements d'intégration : Utilisés pour communiquer entre les contextes délimités. Ceux-ci devraient être des messages de cohérence éventuelle. Ils doivent être stables, versionnés et transportés via un courtier (Kafka, RabbitMQ, etc.).

    Ne publiez pas directement vos événements de domaine internes sur un courtier de messages. Mappez-les d'abord vers des événements d'intégration. Cela vous permet de refactoriser votre domaine interne sans casser les contrats externes.

Gestion de la version et de la rétrocompatibilité

Les événements sont des messages à longue durée de vie. Une fois publiés, ils peuvent être consommés par des systèmes que vous ne contrôlez pas. Traitez les schémas d'événements comme des API publiques.

  • N'enlevez jamais de champs : Ajoutez de nouveaux champs à la place. Les consommateurs peuvent ignorer les champs inconnus.
  • Versionnez vos événements : Si un changement cassant est nécessaire, créez une nouvelle version de l'événement (par ex., CommandePasséeV2) et exécutez les deux en parallèle pendant une période de transition.
  • Utilisez des registres de schémas : Des outils comme Confluent Schema Registry appliquent des vérifications de compatibilité avant le déploiement.

Conclusion

Les événements de domaine sont un outil puissant pour construire des systèmes évolutifs et découplés en DDD. Cependant, leur puissance s'accompagne de responsabilités. En adhérant à des conventions de nommage claires, en gardant les charges utiles minimales et ciblées, et en séparant strictement les événements de domaine des événements d'intégration, vous pouvez construire des systèmes résilients au changement et faciles à faire évoluer.

Rappelez-vous : Si vos abonnés doivent savoir comment l'événement a été produit, vous avez fuité des détails d'implémentation. S'ils n'ont besoin que de savoir ce qui s'est passé et ce qu'ils doivent faire, vous avez conçu un bon événement.

Bon code !

Share: