Introduction à Dropwizard

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ètre echo 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.