10 conseils pour réussir ses premiers pas en DDD

tourainetech

Me

avatar
  • Développeur Java depuis plus de 10 ans
  • Commiteur Asciidoctor (project lead asciidoctor-ant)
  • Technical Leader chez Lectra, numéro un mondial des solutions dédiées à l’industrie du textile (machines et logiciels)
lectra

Domain Driven Design

Tackling Complexity in the Heart of Software
Eric Evans, 2003
blue book

Pourquoi DDD ?

twitter cyriux
  • comprendre le domaine métier
  • réduire la complexité accidentelle pour se concentrer sur la complexité métier
  • mieux communiquer avec les différents intervenants

Conseil 1

DDDViteFait
1) Lire Domain-Driven Design Vite fait

Conseil 2

2) Mettre le focus sur la modélisation du métier
  • Permet de définir un vocabulaire (Ubiquitous Language)
  • Bien réfléchir aux propriétés des entités
  • Bien définir des agrégats
  • Rester indépendant de tous composants techniques (persistence, sérialisation)

Exemple : Pokemon

pikachu

Un Pokemon :

  • A un type (pikachu)
  • A un nom (par défaut type)
  • A des PV (par défaut 100)
  • Peut être nommé
  • Peut évoluer
  • Peut mourir …​

POJO : ce n’est pas un modèle !

public class Pokemon {

    private Long id;
    private String type;
    private String name;
    private int pv;

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getPv() {
        return pv;
    }
    public void setPv(int pv) {
        this.pv = pv;
    }
}

Constructeur et propriété

public class Pokemon {

    private final String id;
    private final PokemonType type;
    private String name;
    private int pv;

    public Pokemon(PokemonType type) {
        this.id = UUID.randomUUID().toString();
        this.type = Objects.requireNonNull(type);
        this.name = type.value();
        this.pv = 100;
    }

    public String id() {
        return id;
    }

    public PokemonType type() {
        return type;
    }

    public String name() {
        return  name;
    }

Métier dans le modèle

public boolean isDead() {
        return this.pv == 0;
    }

    public void die() {
        this.pv = 0;
    }

    public void rename(String newName) {
        this.name = newName;
    }

    public Optional<Pokemon> evolve() {
        return type.evolution()
                .map(evolutionType -> {
                    Pokemon pokemon = new Pokemon(evolutionType);
                    pokemon.name = this.name;
                    pokemon.pv = this.pv;
                    return pokemon;
                });
    }

Conseil 3

twitter ouarzy
  • Commencer par une persistence en mémoire (hashmap) pour éviter de lier son domaine à des problèmatiques techniques
  • Utiliser des UUIDs générés par l’application comme id

Conseil 4

4) S’appuyer sur une architecture hexagonale
alistair

Ports & Adapters

ddd hexagonal

Dépendances dans l’hexagone

hexagonal

Conseil 5

5) La couche application doit définir des cas d’utilisation
  • un cas d’utilisation → une classe avec une seule méthode
  • pas d’appel entre deux services d’application
  • permet d’écrire des tests indépendamment du protocole de l’interface

Exemple : Evolution Pokemon

public class EvolvePokemon {

    private final PokemonBox box;

    public EvolvePokemon(PokemonBox box) {
        this.box = box;
    }

    public Pokemon execute(Trainer trainer, Pokemon pokemon) {
        if (trainer.candies() > 50) {
            Pokemon evolvedPokemon = pokemon.evolve()
                    .orElseThrow(() -> new RuntimeException("This pokemon cannot evoluate"));
            this.box.remove(pokemon);
            this.box.add(evolvedPokemon);
            trainer.removeCandies(50);
            return evolvedPokemon;
        }
        throw new RuntimeException("Not enough candies to evolve this pokemon");
    }
}

Exemple : Catch Pokemon

public class CatchPokemon {

    private final PokemonBox box;

    public CatchPokemon(PokemonBox box) {
        this.box = box;
    }

    public void execute(Trainer trainer, Pokemon pokemon) {
        if (pokemon.isDead()) {
            throw new RuntimeException("Cannot catch dead pokemon !");
        }
        this.box.add(pokemon);
        trainer.addCandies(3);
    }
}

Conseil 6

6) Bien comprendre la notion de service
  • Service d’applications : cas d’utilisation
  • Service d’infrastructure : communication avec les systèmes externes
  • Service du domaine : règle métier partagée entre deux cas d’utilisation

Exemple : Domain Service

public class PokemonStatsCalculation {

    private final Pokedex pokedex;

    public PokemonStatsCalculation(Pokedex pokedex) {
        this.pokedex = pokedex;
    }

    int attack(Pokemon pokemon) {
         return awesomeAlgo(pokedex.baseStatAttack(pokemon));
    }

    int defense(Pokemon pokemon) {
        return awesomeAlgo(pokedex.baseStatDefense(pokemon));
    }

    int stamina(Pokemon pokemon) {
        return awesomeAlgo(pokedex.baseStatStamina(pokemon));
    }

Conseil 7

living doc
7) Living Documentation FTW !
  • Annotations :

    • @DDD.Entity, @DDD.Repository, @DDD.DomainService, …​

  • Générer un diagramme du domaine à partir des sources
  • Générer un diagramme des dépendances de l’architecture hexagonale

Conseil 8

8) Se débarrasser de ses à-priori de développeur !
  • Des habitudes de développement liées à notre apprentissage des frameworks…​
  • Le plus important c’est le métier pas la technique !

Exemple : Annotation JPA

public class Pokemon {

    @Id
    private final String id;
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="value", column=@Column(name="TYPE"))
    })
    private final PokemonType type;
    @Column(name = "name", columnDefinition = "VARCHAR(255)")
    private String name;
    @Column(name = "pv",length=2, nullable = false)
    private int pv;
    ....
}

XML configuration !

  • Mapping JPA par annotations pollue le domaine
  • Utilisation de META-INF/orm.xml

    • moyen de sortir le framework du domaine
    • tout aussi facile à écrire et à maintenir
    • support dans l’ide

Conseil 9

9) Faire des tests d’intégration en utilisant le Behavior Driven Development
  • Permet de manipuler le domaine dans les tests (fichier feature)

Exemple de feature

Scenario: Catch a pokemon
   Given a pikachu with PV=512
   When you give a cloudberry
     And you throw a pokeball
   Then the pikachu is caught
     And you win 500 XP
cucumber
pokemongo

Conseil 10

10) Impliquer son Product Owner
  • Lui expliquer les concepts, la démarche
  • Lui montrer le modèle
  • Lui demander si le vocabulaire lui parle
  • Lui montrer les tests BDD
  • Lui demander de les écrire ;-)

Conclusion : Not a silver bullet but …​

twitter ouarzy2
  • Permet de passer une étape dans votre parcours de développeur !

/