vendredi 5 mars 2010

DDD les doigts dans le nez...

... avec Play!!
Vous connaissez peut être l'approche DDD, ou Domain Driven Design qui consiste à centrer sa conception autour du modèle métier, ce qui permet entre autres d'arriver à un style plus orienté objet que ce que l'on a généralement avec nos architectures n-tiers et nos modèles métiers anémiques, contenant uniquement des variables et des getters/setters, sans aucune intelligence.
Je triche un peu, le concept de DDD est en réalité beaucoup plus large que ça :
  • design centré sur le fonctionnel
  • expressivité métier du code
  • définition d'un vocabulaire partagé par les gens du métier et les développeurs
  • ...
Si vous voulez en savoir plus je vous invite à lire cette série de billets du Touilleur Express.
Mais voyons déjà comment se passer d'une couche service très procédurale et enrichir notre domaine métier en utilisant Play! framework.
Dans cet exemple, nous modélisons une CDthèque. Un album est défini par un nom et un artiste.
Nous voulons appliquer cette règle : lorsqu'un utilisateur ajoute un album, on lui demande le nom de l'album et le nom de l'artiste. Cependant si l'artiste existe déjà dans notre référentiel, on ne veut pas le dupliquer.

Voyons comment modéliser ça avec Play!. Notre règle métier ne doit pas être visible dans le contrôleur. Elle est cachée dans notre classe Album. Si on veut connaître les règles métier de l'application, c'est bien les classes du domaine que l'on doit regarder. On veut donc résumer le code du controlleur à "je récupère l'album et l'artiste de l'écran, puis je met cet artiste dans cet album".

Le code du contrôleur se résume alors à ça pour la partie création :

public static void save(@Valid Album album, Artist artist) {
        //set the album
        album.artist=artist;
        album.save();
        
        //go back to list page;
        list();
    }

On passe l'artiste à l'album et on sauvegarde l'album. C'est tout.
Toute la magie se passe dans la classe Album. Premièrement vous remarquerez que je n'ai pas utilisé de setter pour passer l'artiste dans l'album. En réalité, play! s'occupe de ça pour nous et à la compilation c'est bien la méthode setArtist() qui est utilisée. J'ai d'ailleurs redéfini cette méthode pour y ajouter la règle évoquée plus haut :

@Entity
public class Album extends Model {
    @Required
    public String name;
    @ManyToOne
    public Artist artist;

    public void setArtist(Artist artist){
        System.out.println(artist.name);
        List<artist> existingArtists = Artist.find("byName", artist.name).fetch();
        if(existingArtists.size()>0){
            //Artist name is unique
            this.artist=existingArtists.get(0);
        }
        else{
            this.artist=artist;
        }
    }

    @Override
    public Album save(){
        //save artist if transient
        if(artist.id==null)
            artist.save();
        return super.save();
    }
}

NB: l'écriture des getters/setters n'est pas obligatoire, s'ils ne font qu'accéder aux champs de la classe inutile de les écrire, play! génèrera ces méthodes automatiquement.

Comme vous pouvez le constater, avec l'architecture de play! j'accède très facilement aux méthodes d'accès à ma base de données depuis mon entité Album. Les entités n'étant généralement pas "managées" par un quelconque conteneur, contrairement à un Spring bean ou un EJB, y injecter des services, des DAO ou un entity manager JPA peut vite devenir assez compliqué selon les technologies que l'on utilise. Ici pas de soucis, Play! nous propose une architecture propre et cohérente qui nous permet de faire tout cela sans injection de dépendance. Vous remarquerez au passage la facilité avec laquelle on effectue notre requête JPA, avec les mots clés spécifique à Play! comme le "byName".

Dernier point, pour sauvegarder l'abum j'ai redéfini la méthode save, dans laquelle je sauvegarde d'abord l'artiste s'il n'est pas encore créé en base.

Conclusion : on a bien isolé notre contrôleur de nos problématiques métiers, et on a enrichi notre couche du domaine! Ce n'est qu'une étape mais maintenant vous savez que si vous vous lancez dans une vraie conception DDD, vous n'aurez pas de problèmes techniques avec Play! pour créer un modèle non anémique!