La dague et la baguette magique : Utilisation de Dagger dans une application Dropwizard

Il était une fois…

J’ai écrit il y a quelque temps un article pour présenter le framework Dropwizard, si vous ne l’avez pas lu commencez par là, car voici la suite.

Petit rappel, Dropwizard est un framework pour réaliser des API REST avec une approche fullstack. Cependant comme vous avez pu le remarquer il manque à celui-ci une fonctionnalité qui me semble réellement importante : l’injection de dépendances. La tuyauterie de l’application se fait de façon manuelle dans la méthode run du Service, et pour peu qu’on ait une application qui dépasse la simple création de Todos, cela peut rapidement devenir le bazar.

Je me suis donc mis en tête de mettre de l’ordre dans mon application en utilisant un framework de DI. Oui mais lequel choisir ?

  • Spring : la réponse évidente ! Il existe d’ailleurs une intégration avec Dropwizard. Cependant, selon moi, Spring risque de poser quelques problèmes avec notre cas d’utilisation. Tout d’abord Spring utilise le format XML pour sa configuration (à moins d’utiliser exclusivement la JavaConfig) alors que Dropwizard utilise le format YAML, rien d’insurmontable mais pas idéal pour autant. Ensuite la philosophie de Dropwizard est d’avoir une stack légère, y ajouter Spring qui a aussi une approche fullstack, n’est en fait peut être pas nécessaire. On a plutôt besoin ici d’une bibliothèque légère qui se concentre exclusivement sur l’injection de dépendances. Et puis mince j’avais besoin de nouveauté.
  • Guice : voilà une bibliothèque d’injection de dépendances basée sur l’utilisation d’annotation et au besoin de code Java, ce qui résout notre problème XML vs YAML, on charge la configuration avec Dropwizard et on passe l’objet Java ainsi créé à Guice. Cependant j’avais encore besoin de nouveauté, alors pourquoi ne pas s’intéresser au dernier rejeton de Guice…
  • Dagger : c’est une bibliothèque d’injection de dépendances grandement inspirée par Guice qui au lieu d’utiliser la réflexion au runtime, utilise la génération de code à la compilation pour réaliser l’injection. Pourquoi ? Pour des raisons de performance, Dagger a été créée par Square pour remplacer Guice dans leurs applications Android. En effet la réflexion a longtemps souffert de problème de performance sur Android, et le temps de lancement d’une application mobile est bien plus critique que pour une application serveur. Si l’application ne se lance pas en quelques secondes, il y a bien des chances pour que l’utilisateur ne l’utilise plus jamais ! Donc réaliser le maximum d’opérations à la compilation et non au runtime est relativement sensé. De plus on gagne aussi une plus grande sûreté car un certain nombre de vérifications vont pouvoir être faites dès la compilation.

Injectons mes amis ! Injectons !

Au vu de la taille de la complexité de mon application, il ne devrait pas y avoir grande embûche à utiliser une libraire de DI. Voyons donc cela.

Commençons par ajouter Dagger à notre pom.xml :

1
2
3
4
<dependency>
<groupId>com.squareup.dagger</groupId>
<artifactId>dagger</artifactId> <version>1.0.0</version>
</dependency>

Occupons-nous maintenant de notre TodoResource dans laquelle nous avons besoin d’injecter un TodoDAO. Je vais ici utiliser l’injection par constructeur car mon objet est immutable. Pour cela rien de très compliqué il suffit d’ajouter l’annotation @Inject sur le constructeur qui va servir à Dagger pour construire notre ressource :

1
2
3
4
@Inject
public TodoResource(TodoDAO todoDAO) {
this.todoDAO = todoDAO;
}

Il faut maintenant que je fournisse à Dagger le moyen d’injecter un TodoDAO. Or mon JDBITodoDAO est une interface, pas moyen d’annoter un constructeur avec @Inject. Je vais donc devoir expliquer à Dagger comment créer une instance de TodoDAO à partir de mon interface pour cela je vais devoir créer un module. Ici encore rien de très compliqué. Un module = un POJO annoté avec @Module.

1
2
3
4
5
6
7
8
9
@Module
public class TodoModule {
private final TodoConfiguration configuration;
private final Environment environment;
public TodoModule(TodoConfiguration configuration, Environment environment) {
this.configuration = configuration;
this.environment = environment;
}
}

Il faut maintenant expliquer comment produire un TodoDAO, pour cela une méthode renvoyant le type souhaité et annotée avec @Provides suffit :

1
2
3
4
5
6
7
8
9
10
11
12
13
@Provides
// Contrairement à Spring le comportement par défaut est de créer une instance à chaque injection.
// On doit donc indiquer explicitement que l'on veut un singleton
@Singleton
public TodoDAO provideTodoDAO() {
try {
final DBIFactory factory = new DBIFactory();
final DBI jdbi = factory.build(this.environment, this.configuration.getDatabaseConfiguration(), "postgresql");
return jdbi.onDemand(JDBITodoDAO.class);
} catch(ClassNotFoundException cnfe) {
throw new RuntimeException(cnfe);
}
}

Nous ne sommes plus très loin du but. Il nous reste à bootstraper notre graphe d’objets dans la méthode run de notre TodoService> et de récupérer une instance de notre TodoResource :

1
2
3
4
5
@Override
public void run(TodoConfiguration configuration, Environment environment) throws Exception {
ObjectGraph objectGraph = ObjectGraph.create(new TodoModule(configuration, environment));
environment.addResource(objectGraph.get(TodoResource.class));
}

On compile notre projet et là…

1
2
3
4
5
[ERROR]
/home/bastien/Workspace/dropwizard-todo/src/main/java/fr/blemale/dropwizard/todo/TodoModule.java:[20,8]
Graph validation failed: You have these unused @Provider methods: 1.
fr.blemale.dropwizard.todo.TodoModule.provideTodoDAO() Set
library=true in your module to disable this check.

En effet Dagger réalise un certain nombre de validations à la compilation. Il vérifie entre autre que les bindings déclarés sont bien utilisés. Or au moment de construire le graphe d’objets, Dagger ne sait pas que nous allons récupérer une instance de TodoService dans la suite du code, notre fournisseur de TodoDAO se retrouve inutilisé. Pour résoudre ce problème il faut indiquer que nous allons récupérer manuellement une instance de TodoService à partir du graphe d’objets généré. Il faut ajouter à notre module ces quelques caractères :

1
2
3
4
@Module(injects = {TodoResource.class})
public class TodoModule {
//...
}

On compile à nouveau le projet, on lance le jar et là magie, tout se passe comme sur des roulettes.

Disclaimer : ceci n’était qu’un cours apperçu

Après ma courte utilisation de Dagger, cette bibliothèque me semble intéressante à plusieurs niveaux. Certes elle offre moins de fonctionnalités et d’intégration clés en main que Spring ou même Guice. Mais si les fonctionnalités de Dagger vous suffisent, vous avez alors une bibliothèque très légère, qui en utilisant la génération de code à la compilation, vous fournit un certain nombre de vérifications dès la compilation et un temps de lancement de votre application plus court. Alors pourquoi ne pas essayer.

Le code source se trouve bien évidemment sur Github.

VN:R_U [1.9.22_1171]
Rating: 0 (from 0 votes)
Share
Ce contenu a été publié dans Java, avec comme mot(s)-clef(s) , , , . Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire