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
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
  • 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 Argent DébiterCompte(Argent compte, Argent paiement, IServiceDevise serviceDevise)
          {
            try
            {
              return compte.Soustraire(paiement);
            }
            catch (ExceptionDevise)
            {
              var paiementConverti = serviceDevise.Convertir(paiement, compte.Devise);
              return compte.Soustraire(paiementConverti);
            }
          }

          CalculerPaiement(new Argent(50.55, Devise.CAD), new Argent(0.45, Devise.USD), 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 ![New Architecture](diagrams/out/src/diagrams/new-architecture.svg) Notes: C'est beaucoup en même temps, on va passer chacune des étape en détails! Ceci est une représentation de ce qui est arrivé au final, j'ai évidemment itéré plusieurs fois et chaque logiciel est unique!
## 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