Faire évoluer un logiciel existant avec le **Domain-Driven Design** et l'**Event Sourcing**

Christian Droulers

Architecte Logiciel, Akinox Solutions

Objectifs de la présentation

  • Comprendre le Legacy
  • Rendre la logique simple avec le DDD
  • Créer des points d'extension avec le ES
  • Remplacer les anciennes interfaces du projet existant

Interface Legacy avant

Interface malade maintenant

Notes: Test: Indicateur principal car les autres en découle plus ou moins! Architecture: Mauvaise ré-utilisation de code, copier/coller, etc. Peur: Trop de travail, toujours des imprévus, on réduit les fonctionnalités Ancien: Vieilles technologies, code archaïque et hétérogène Programmeur: Impossible d'obtenir une explication sur certaines choses bizarres.
## Base de données ![Legacy Database](images/legacy-db.png) Notes: **RAPIDE** * Pas de FK, mauvaise normalisation * Logique éparpillée entre stored proc et code
## Monolithique ![Legacy Code](images/legacy-code.png) Notes: **RAPIDE** * Couplage extrêmement élevé * Code copié/collé Un monolithe est difficile à modifier. Chaque changement peut entrainer une cascade d'erreur de compilation, de tests brisés ou de bogues difficiles à attraper.
## Résultat ![Legacy Code](images/spaghetti-code.jpg) Notes: Il est même difficile de commencer à le tester car il n'a pas été écrit pour et il faut refactorer aggressivement, ce qui prend énormément de temps!

Rendre la logique simple avec le *DDD*

Objectifs du *Domain-Driven Design*

  • Approche développement itérative
  • Focus sur les règles d'affaires
  • Collaboration entre les parties avec un langage commun
  • Crée un modèle beaucoup plus proche du domaine d'affaires

Langage Commun (*Ubiquitous Language*)

  • Définition des termes du domaine d'affaires
  • Permet d'être rigoureux dans le nommage du code
  • Simplifie la communication avec les experts du domaine

Aggrégat (*Aggregate*)

  • Représente une grappe d'objet
  • Traité comme une seule entité consistente
  • Modifications atomique

Racine d'Aggrégat (*Aggregate Root*)

  • Objet principal
  • Reçoit les opérations
  • Point d'entrée unique

Code du Patient


public class Patient : RacineDaggregat
{
  private readonly List<Prescription> prescriptions;

  public ReadOnlyCollection<Prescription> Prescriptions { get; }

  public void Prescrire(Id idMedicament, Unite quantite, Recurrence recurrence)
  {
    if (this.prescriptions.Any(x => x.IdMedicament == idMedicament))
    {
      throw new ExceptionMedicament("Médicament déjà prescrit!");
    }

    this.prescriptions.Add(new Prescription(idMedicament,  quantity, recurrence));
  }
}
          

Code du AggregateRoot


public class RacineDaggregat
{
  public Id Id { get; private set; }
}
          

*Value Object*

  • Objet Immuable identifié uniquement par ses valeurs
  • Rend explicite un concept du domain d'affaire
  • Définit des comportements de ce concept
  • Exemples
    • Une valeur monétaire
    • Une plage de dates
    • Une récurrence de médicaments
    • Un identifiant avec un format spécifique

*Value Object* Argent


public struct Argent
{
  public readonly decimal Valeur;
  public readonly Devise Devise;

  public Argent Additioner(Argent autre)
  {
    if (this.Devise != autre.Devise)
    {
      throw new ExceptionDevise(
        "Veuillez convertir l'argent à la devise " +
          this.Devise.ToString());
    }

    return new Argent(
      this.Valeur + other.Valeur,
      this.Devise);
  }
}
          
## Utilisation de la classe Argent

          public static void DebiterCompte(
            Compte compte,
            Argent paiement,
            IServiceDevise serviceDevise)
          {
            try
            {
              compte.Balance = compte.Balance.Soustraire(paiement);
            }
            catch (ExceptionDevise)
            {
              var converti = serviceDevise.Convertir(paiement, compte.Devise);
              compte.Balance = compte.Balance.Soustraire(converti);
            }
          }

          var montant = new Argent(0.45, Devise.USD);
          DebiterCompte(compte, montant, serviceDevise);
          

Contexte (*Bounded Context*)

  • Domaine d'affaire large et complexe
  • Plusieurs différentes groupes de données et d'interactions
  • Les contextes regroupent ces détails

Créer des points d'extension avec le *Event Sourcing*

Objectifs de l'*Event Sourcing*

  • Chaque changement d'état est capturé dans un événement
  • Les événements sont complets et ne nécessite pas de données externes
  • Ils sont enregistrés pour reconstruire l'état présent
  • Séparer les modèles de lecture et les événements

Avantages de l'*Event Sourcing*

  • Historique complet des changements d'état
  • Requête temporelle
  • Ré-éxécution d'événement erronée

## Événements de Domaine (*Domain Events*) Contiennent l'information représentant l'action complète de l'utilisateur ou du système.

Gestionnaire d'événements (*Event Handlers*)

  • Une action désirée, un gestionnaire
  • Court et simple
  • Multiples destinations

*Repository*

  • Lis les aggrégats
  • Enregistre les nouveaux événements

public interface IRepository
{
  Task<T> LireAsync<T>(IdAggregat id) where T : RacineDaggregat;

  Task EnregistrerAsync<T>(T aggregate) where T : RacineDaggregat;
}
          
## Code du patient

public class Patient : RacineDaggregat
{
  private readonly List<Prescription> prescriptions;

  public ReadOnlyCollection<Prescription> Prescriptions;


  public void Prescrire(Id idMedicament, Unite quantite, Recurrence recurrence)
  {
    if (this.prescriptions.Any(x => x.IdMedicament == idMedicament))
    {
      throw new ExceptionMedicament("Médicament déjà prescrit!");
    }

    this.Dispatcher(new PrescriptionÉmise(idMedicament, quantite, recurrence));
  }
  
  public void Appliquer(PrescriptionÉmise evt)
  {
    this.prescriptions.Add(new Prescription(idMedicament,  quantity, recurrence));
  }
}
          
## Code du AggregateRoot

public class RacineDaggregat
{
  public Id Id { get; private set; }

  private readonly List<Événement> evenements = new List<Événement>();

  protected void Dispatcher(DomainEvent evt)
  {
    this.evenements.Add(evt);

    // Magie de réflection
    this.Appliquer(evt);
  }

  public void LireHistorique(IEnumerable<Événement> evenements)
  {
    foreach (var evt in evenements)
    {
      // Magie de réflection
      this.Appliquer(evt);
    }
  }
}
          

Lecture des données legacy

Remplacer les anciennes interfaces du projet existant

Nouvelle architecture

## Nouvelle interface et API * Interface Web SPA créée avec React / GraphQL / Relay * API GraphQL et .NET core
## Modernité maximale * Utilisation de DDD / ES dans l'API
## Création de patient dans l'API

  public class AjoutPatient
  {
    public async Task Executer(IRepository repo, dynamic input)
    {
      var patient = new Patient(input.prénom, input.nom);

      string idHopital = input.idHopital, numeroIndex = input.numeroIndex;
      if (!string.IsNullOrWhiteSpace(idHopital) &&
        !string.IsNullOrWhiteSpace(numeroIndex))
      {
        var hopital = await repo.LoadAsync<Hopital>(new IdAggregat(idHopital));
        patient.LierHopital(hopital.Id, numeroIndex);
      }

      await repo.EnregistrerAsync(patient);
    }
  }
          
## Gestionnaire d'événements vers multiples destinations * 2 destinations principales * Legacy * Statistiques / Intelligence d'affaire
## Intégration dans le Legacy

Intégration avec le *legacy*

  • Tenir à jour l'ancienne base de données
  • Remplacer des vieilles interfaces par des nouvelles si possible
  • Retourner les actions pertinentes au nouveau code

Succès concrets

  • Reconstruction des statistiques après bogue ou modifications
  • Ajout de nouveaux gestionnaires d'événements facilement
  • Audit de modification automatique
  • Logique d'affaire simplifiée
  • Prototype d'application Offline-first sans modifier le domaine.

Questions?







akinox.com/fr/carrieres

Faire évoluer un logiciel existant avec le **Domain-Driven Design** et l'**Event Sourcing**

Christian Droulers

Architecte Logiciel, Akinox Solutions

### Références ![Domain-Driven Design](images/ddd_book_eric_evans.jpg) ![Domain-Driven Design](images/ddd_book_vaughn_vernon.jpg) ![Domain-Driven Design](images/ddd_book_vaughn_vernon2.jpg) Notes: Trois livres intéressants à lire pour approfondir ses connaissances en DDD. C'est un sujet très complexe