Ceux qui ont eu la chance (?) de bosser avec moi savent que, en termes d'architecture des applications Java, je défends le concept de modèle métier riche, par opposition au Anemic Model Domain.
L'idée part d'un constat simple : on a souvent un beau modèle objet du domaine métier, bien adapté pour accueillir les données de l'application, et on déporte toute la logique de leur gestion dans des classes techniques spécialisées, qu'on appelle des "services" parce que c'est un mot qui peut dire tout et n'importe quoi alors pourquoi se priver ?
On se retrouve donc avec un modèle métier qui ne fait rien, noyé dans des couches de services métier, de services web, de services techniques (celui-là je l'adore) et de DAO (qu'on a pas appelé service pour une fois).
Avec JPA et ses annotations, on peut rapidement réduire la couche DAO à peu de choses, voir l'éliminer purement et simplement comme je l'ai fait avec Fonzie (un jour, promis, je reprendrai le développement ce projet ... quand j'aurais le temps). JPA étant déjà une abstraction de la persistance, je ne vois aucun intérêt à rajouter une couche DAO pour faire une requête JPA. En quoi se faire injecter un DAO est plus "propre" que se faire injecter un PersistenceContext et utiliser des NamedQueries ?
Pour aller plus loin, c'est à dire faire que nos objets métier portent eux-même la logique métier, on est rapidement confronté à un problème : nos objets ont besoin de faire appel à d'autres objets "collaborateurs" pour faire autre chose que de la conversion franc-euro. Spring propose depuis 2006 une solution via l'annotation @Configurable et l'instrumentation par AspectJ. Pour l'avoir utilisé sur un gros projet qui se reconnaitra, ça marche bien mais c'est un peu encombrant pour les développeurs (oui, AJDT, je pense à toi). Depuis, Java EE 6 est sorti et propose CDI.
Pour des besoins internes, j'ai développé une application d'intégration de services REST déployée sur RUN@Cloud. N'en déplaise à mes collègues, pas de Scala, d'Erlang ou de Ruby ici, mais du Java EE 6 brut de spécification, histoire de vérifier si Antonio raconte des bobards pendant ses conférences.
J'ai un objet métier Ticket, que je veux exposer avec JAX-RS :
@Path("/ticket/{id}") public class Ticket { private int id; public Ticket(@PathParam("id") int it) { this.id = id; // Load data from id; } }
Cela fonctionne très bien tant qu'il s'agit de faire un System.out, maintenant j'aimerais faire des choses plus concrètes, nécessitant l'utilisation d'un autre composant.
Après quelques errements, et avec l'aide de Paul, j'ai compris comment activer CDI pour obtenir l'injection de mes dépendances. Comme j'ai peur de ne pas m'en souvenir pour la prochaine fois, et que ça peut servir à d'autres, voici la recette : d'abord placer le fameux fichier bean.xml vide sous WEB-INF, puis modifier mon code pour CDI-fier ma classe JAX-RS :
@Path("/ticket/{id}") @RequestScoped public class Ticket { @PathParam("id") private int id; @Inject private AnotherComponent foobar; @PostConstruct public void load() throws IOException { // Load data from id; } @POST @Produces("text/plain") public String notify() throws Exception { foobar.sendNotification(id, getEmail()); return "ok"; } }
petite explication :
Dans un premier temps j'avais utilisé un constructeur prenant en paramètre l'ID du chemin REST et chargeant les données. Couplé avec CDI, je dois annoter ce constructeur @Inject pour qu'il soit utilisé à la place du constructeur par défaut, mais CDI dans ce cas cherche à m'injecter l'ID. Je ne sais pas si c'est un bug de RestEasy-cdi dans JBoss, toujours est-il que j'ai du suivre le plan B.
Je récupère donc l'ID par injection d'attribut depuis la requête REST. L'initialisation du bean est confiée à un @PostConstruct, ce qui en termes de gestion du cycle de vie est nettement plus explicite que de tout mettre dans le constructeur.
Et voila, mon Ticket est désormais un objet de mon domaine métier, exposé comme service REST, qui sur un POST gère un traitement métier via des composants avec qui il collabore et qu'il obtient par injection de dépendance. Pas besoin de TicketService, TicketResource ou autre classe purement artificielle juste introduite pour respecter un joli modèle en couches.