Dropwizard est un framework complet et éprouvé (première version publiée en 2011) pour faciliter le développement de services web “RESTful”. Il s’agit en fait du regroupement et de l’intégration de plusieurs outils du monde Java, parmi lesquels : Jetty, Jersey, Jackson, Logback, Liquibase, Hibernate…
J’ai eu l’occasion d’utiliser cet outil et je le trouve particulièrement efficace, notamment pour mettre en place rapidement un ensemble de micro-services, ou un back-end simple pour une application de type Single Page Application (AngularJS, etc.). Dans l’idée, Spring Boot est assez similaire, si ce n’est que Dropwizard n’est pas ancré dans l’écosystème Spring.
Je vous propose ici un rapide tour d’horizon des différentes fonctionnalités de l’outil.
Pour commencer
Avec Maven, le démarrage d’un projet Dropwizard est plutôt simple. Il suffit d’ajouter la dépendance à la dernière version du projet “core” (0.7.1) :
<dependencies>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
<version>0.7.1</version>
</dependency>
</dependencies>
Un peu de configuration
Chaque application Dropwizard a besoin d’une classe de configuration qui étend io.dropwizard.Configuration
. Voici notre classe de configuration, très simple, pour démarrer ce tutoriel :
public class MyConfiguration extends Configuration {
@NotEmpty
private String environment;
@JsonProperty
public String getEnvironment() {
return environment;
}
@JsonProperty
public void setEnvironment(String environment) {
this.environment = environment;
}
}
Rien d’extraordinaire ici, mais il faut noter deux choses :
- La configuration sera dé-sérialisée depuis un fichier YAML par Jackson. Donc il est possible (voir recommandé) d’utiliser les annotations de Jackson !
- La configuration est validée avec Hibernate Validator ! La liste des contraintes possibles est vaste (voir ici pour la liste complète), et l’application ne démarrera pas si le fichier de configuration n’est pas valide !
Voici le contenu du fichier de configuration correspondant à notre classe :
environment: "dev"
Une application…
Passons maintenant au coeur de notre application, la classe MyApplication qui étend io.dropwizard.Application
.
public class MyApplication extends Application<MyConfiguration> {
public static void main(String[] args) throws Exception {
new MyApplication().run(args);
}
@Override
public String getName() {
return "my-application";
}
@Override
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
// TODO Auto-generated method stub
}
@Override
public void run(MyConfiguration configuration, Environment environment)
throws Exception {
// TODO Auto-generated method stub
System.out.println(getName() + " is now running on " +
configuration.getEnvironment() + " environment !");
}
}
Là encore, rien d’extraordinaire. Un simple classe avec un main
qui délègue son exécution à la méthode run
. La méthode initialize
permet de configurer des composants avant le lancement de l’application.
…pleine de ressources !
Et pour finir, une ressource très simple qui nous fera de l’écho :
@Path("/echo")
@Produces(MediaType.TEXT_PLAIN)
public class EchoResource {
@GET
@Timed
public String echo(@QueryParam("echo") Optional<String> echo) {
if(echo.isPresent()) {
return echo.get();
} else {
throw new NotFoundException();
}
}
}
Quelques remarques à propos de cette classe :
- Elle utilise les annotations “standard” de JAX-RS
- L’annotation
@Timed
permet à Dropwizard de récolter des métriques sur les appels à cette méthode - Dropwizard ajoute le support natif des
Optional
de Guava à Jersey. Ainsi, si le paramètreecho
n’est pas passé, Optional vaudra absent, sinon il contiendra la valeur du paramètre.
Il ne nous reste plus qu’à déclarer notre ressource dans la méthode run
de la classe principale avec un simple environment.jersey().register(new EchoResource());
, et on peut passer au démarrage de notre application !
Lancement de l’application
Une des préconisations de la documentation Dropwizard, c’est de construire un fat JAR contenant tous les .class de notre application, y compris les dépendances.
Cela permet de lancer l’application avec un simple java -jar monJar.jar
. Pour configurer Maven pour packager un fat JAR, il suffit d’ajouter le plugin suivant dans la sections build > plugins
de votre pom.xml
:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.6</version>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer" />
<mainClass>fr.ippon.blog.example.dropwizard.intro.MyApplication</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
Sans oublier de changer la classe principale de l’application.
L’exécution de la commande java -jar monJar-0.0.1-SNAPSHOT.jar server dev.yaml
démarre l’application dans un serveur Jetty embedded.
Un rapide test nous donne le résultat escompté :
$ curl localhost:8080/echo?echo=Hello,%20world!
Hello, world!
$
L’administration
Une des valeurs ajoutées de Dropwizard, c’est certainement l’interface d’administration disponible sur le port 8081 une fois l’application lancée.
$ curl http://localhost:8081/metrics?pretty=true
{
"version" : "3.0.0",
"gauges" : {
...
"jvm.memory.total.max" : {
"value" : 3797417983
},
"jvm.memory.total.used" : {
"value" : 146297864
},
"jvm.threads.count" : {
"value" : 37
},
...
},
"counters" : {
"io.dropwizard.jetty.MutableServletContextHandler.active-dispatches" : {
"count" : 0
},
"io.dropwizard.jetty.MutableServletContextHandler.active-requests" : {
"count" : 0
},
"io.dropwizard.jetty.MutableServletContextHandler.active-suspended" : {
"count" : 0
}
},
"histograms" : { },
"meters" : {
...
"io.dropwizard.jetty.MutableServletContextHandler.4xx-responses" : {
"count" : 3,
"m15_rate" : 2.717586079541239E-5,
"m1_rate" : 5.0046012383445376E-33,
"m5_rate" : 5.610150977893677E-9,
"mean_rate" : 3.759305958043252E-4,
"units" : "events/second"
},
...
},
"timers" : {
"com.jeanchampemont.blog.example.dropwizard.intro.resources.EchoResource.echo" : {
"count" : 8,
"max" : 0.00173718,
"mean" : 4.881765E-4,
"min" : 3.4411E-5,
"p50" : 1.029335E-4,
"p75" : 0.0012517205000000002,
"p95" : 0.00173718,
"p98" : 0.00173718,
"p99" : 0.00173718,
"p999" : 0.00173718,
"stddev" : 7.335333511812145E-4,
"m15_rate" : 2.0470100691740497E-4,
"m1_rate" : 2.914566711610656E-15,
"m5_rate" : 9.373552096002814E-6,
"mean_rate" : 0.0010024795256175021,
"duration_units" : "seconds",
"rate_units" : "calls/second"
},
...
}
En plus d’avoir accès à différentes statistiques sur l’utilisation des ressources de la JVM, des requêtes, des codes de réponse, on remarque que l’annotation @Timed
précédemment ajoutée à notre méthode, ajoute des informations sur ses temps d’exécution et leur répartition statistique.
Bilan de santé
Vous avez peut être remarqué, lors du démarrage de notre application, un message plutôt alarmant dans les logs :
WARN [2014-42-42 42:42:42,642] io.dropwizard.setup.AdminEnvironment:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! THIS APPLICATION HAS NO HEALTHCHECKS. THIS MEANS YOU WILL NEVER KNOW !
! IF IT DIES IN PRODUCTION, WHICH MEANS YOU WILL NEVER KNOW IF YOU'RE !
! LETTING YOUR USERS DOWN. YOU SHOULD ADD A HEALTHCHECK FOR EACH OF YOUR !
! APPLICATION'S DEPENDENCIES WHICH FULLY (BUT LIGHTLY) TESTS IT. !
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
```
Un Health Check (ou bilan de santé) est un test de la disponibilité d’un service externe indispensable au fonctionnement de notre application : connectivité à la base de données, disponibilité de services web tiers, etc.
Je vous propose ici un `StupidHealthCheck` qui échoue de temps en temps (à ne surtout pas utiliser en production donc !).
``` java
public class StupidHealthCheck extends HealthCheck {
private Random rand = new Random();
@Override
protected Result check() throws Exception {
if(rand.nextInt(100) >= 1) {
return Result.healthy();
} else {
return Result.unhealthy("fail");
}
}
}
Comme précédemment, on n’oublie pas de déclarer notre Health Check dans la méthode run
de la classe principale avec un : environment.healthChecks().register("Stupid HealthCheck", new StupidHealthCheck());
.
Une entrée est dès lors disponible dans l’interface d’administration et permet de superviser simplement le bon fonctionnement de l’application.
$ curl http://localhost:8081/healthcheck?pretty=true
{
"Stupid HealthCheck" : {
"healthy" : true
},
"deadlocks" : {
"healthy" : true
}
}
Conclusion
Dropwizard apporte son lot de fonctionnalité et s’intègre avec de nombreux outils Java communément utilisés. Je vous encourage à regarder la documentation officielle pour plus de détails. De mon avis, la seule chose qu’il manque peut être à Dropwizard, c’est l’intégration native d’un framework d’injection de dépendance.