4 mars 2015

Dropwizard


Dropwizard est un framework Java qui est orienté Hautes Performances, Web Services REST et qui produit une application prête à être mise en production en intégrant des services utiles aux "Ops".

Dropwizard est adapté pour la mise en place de micro-services.

En effet, une application produite avec Dropwizard est autonome et autoportée :
  • elle embarque un serveur Jetty pour exposer des services RESTful via Jersey (norme JAX-RS)
  • elle peut utiliser une base mémoire comme H2
Cette application est packagée sous la forme d'un JAR et ne nécessite que la présence du JDK de Java pour fonctionner.

L'application se lance via la commande java -jar avec en paramètre la localisation du fichier YAML de configuration.
java -jar application.jar server config.yml
Les paramètres de configuration qui sont spécifiques à l'environnement cible peuvent être surchargés via la commande java -jar afin de ne pas avoir à modifier à chaque fois le fichier de configuration.

Il est également possible de fournir des services pour l'arrêt et le redémarrage de certains services de l'application (interface Managed) ainsi que pour le changement de fichiers statiques à la volée (Assets).

La présence de métriques via Metrics permet de fournir des informations aux "Ops" (opérations) :
  • état de la JVM
  • état des composants critiques de l'application : 
    • temps de réponses de requêtes, état du pool de connexions JDBC (nombre de connexions actives, en attente), etc.
Les métriques fournies par Metrics peuvent s'interfacer avec des outils de reporting comme Ganglia et Graphite afin d'avoir un tableau de bord pour les "Ops".

Composants fournis avec Dropwizard

Dropwizard intègre et configure automatiquement les composants suivants :
  • Jetty: Serveur HTTP et Servlet Java
  • Jersey: implémentation de la norme JAX-RS de Java EE pour définr des services Web RESTful
  • Jackson: Parser JSON pour Java
  • JDBI: Librairie pour l'exécution de requêtes SQL
  • Logback: Successeur de Log4j
  • Metrics: Enregistre et fournit des métriques de la JVM et de l'application
    • Metrics permet d'avoir une vue de l'état des composants critiques de l'application dans l'environnement de production
    • Pour le reporting, il est possible d'utiliser Ganglia et Graphite
  • Google guava: Bibliothèque de classes utilitaires: collections, cache, support des pritimives, concurrence, annotations, traitement de chaînes de caractères, I/O, etc.
  • Hibernate Validator: Règles de validation via l'utilisation d'annotations
  • Liquibase: Pour gérer les changements de versions de la base de données (dans le module dropwizard-migrations)

Modules Dropwizard

Dropwizard propose les modules suivants :

Configuration Maven

<dependencies>
  <dependency>
    <groupId>io.dropwizard</groupId>
    <artifactId>dropwizard-core</artifactId>
    <version>0.7.1</version>
  </dependency>
  ... plus les dépendances vers les autres modules de Dropwizard
</dependencies>

Exemple d'application Dropwizard

Le repo Github de Dropwizard contient un exemple d'application de type "Hello World" qui permet de tester une application Dropwizard :

L'application d'exemple de Dropwizard utilise H2 comme base de données embarquée et Hibernate pour la persistance des données. Elle expose les services RESTful via Jersey (JAX-RS). Elle utilise Metrics pour fournit des métriques.
Pour utiliser cette application d'exemple :
  • Télécharger le ZIP de Dropwizard disponible sur le repo Github de Dropwizard :
  • Dans le ZIP, dézipper le répertoire dropwizard-exemple
  • Dans ce répertoire dropwizard-exemple
    • Lancer le packaging avec Maven :
    • mvn package -Djar.finalName=dropwizard-example
      
    • Lancer l'application Dropwizard via le JAR généré par Maven :
    • java -jar target/dropwizard-example.jar  server example.yml
L'application d'exemple de Dropwizard se lance.
Nous pouvons appeler les services REST d'exemple via le navigateur qui affiche le résultat en JSON :
L'application fournit également des pages d'administration avec des outils accessibles sur un autre port que celui utilisé pour les services REST et les pages de l'application :
  • http://localhost:8081
    • Metrics : métriques de l'application sous le format JSON
      • http://localhost:8081/metrics?pretty=true
      • Réponse:
      • {
          version: "3.0.0",
          gauges: {
            io.dropwizard.db.ManagedPooledDataSource.hibernate.active: {
              value: 0
            },
            io.dropwizard.db.ManagedPooledDataSource.hibernate.idle: {
              value: 10
            },
            io.dropwizard.db.ManagedPooledDataSource.hibernate.size: {
              value: 10
            },
            io.dropwizard.db.ManagedPooledDataSource.hibernate.waiting: {
              value: 0
            },
        ...
            jvm.memory.heap.init: {
              value: 16777216
            },
            jvm.memory.heap.max: {
              value: 259522560
            },
            jvm.memory.heap.usage: {
              value: 0.07287816519689078
            },
            jvm.memory.heap.used: {
              value: 18913528
            },
        ...
        }
        
    • Ping: pour savoir si l'application est démarrée et répond toujours
    • Threads:
      • http://localhost:8081/threads
      • Réponse:
      • Reference Handler id=2 state=WAITING
            - waiting on <0x01e13dbe> (a java.lang.ref.Reference$Lock)
            - locked <0x01e13dbe> (a java.lang.ref.Reference$Lock)
            at java.lang.Object.wait(Native Method)
            at java.lang.Object.wait(Object.java:503)
            at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source)
        
        Finalizer id=3 state=WAITING
            - waiting on <0x008f2d36> (a java.lang.ref.ReferenceQueue$Lock)
            - locked <0x008f2d36> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.Object.wait(Native Method)
            at java.lang.ref.ReferenceQueue.remove(Unknown Source)
            at java.lang.ref.ReferenceQueue.remove(Unknown Source)
            at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source)
        
        ...
        
    • Healcheck : Etat des composants de l'application

Organisation du projet

Un projet Dropwizard s'organise de préférence de la manière suivante :
  • package com.myapp:
    • MyApplicationConfiguration.java :classe de configuration
    • MyApplication.java :classe principale de l'application
    • packages :
      • core: domaine métier
      • resources: services RESTful
      • view: rendu via des pages HTML
      • db: DAO - accès à la base de données
      • health: Classes HealthCheck : indique l'état des composants de l'application
      • filter: classes de filtrage et d'analyse des requêtes : création de règles de validation via des annotations
      • auth: authentification
      • client: récupération d'informations d'autres services web

Voir sur le projet d'exemple, l'organisation des packages : organisation du projet d'exemple

Configuration

Le fichier YAML de configuration sera parsé par Jackson pour valoriser les propriétés de la classe HelloWorldConfiguration.java.

Fichier YAML de configuration

Contenu du fichier YAML de configuration : example.yml
database:
  driverClass: org.h2.Driver
  user: sa
  password: sa
  url: jdbc:h2:./target/example
server:
  applicationConnectors:
    - type: http
      port: 8080
  adminConnectors:
    - type: http
      port: 8081
logging:
  level: INFO
  loggers:
    com.myapp: DEBUG
    org.hibernate.SQL: ALL
  appenders:
    - type: console

Classe de configuration

Extrait de la classe HelloWorldConfiguration :
public class HelloWorldConfiguration extends Configuration {

    @Valid
    @NotNull
    private DataSourceFactory database = new DataSourceFactory();

    @JsonProperty("database")
    public DataSourceFactory getDataSourceFactory() {
        return database;
    }

    @JsonProperty("database")
    public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
        this.database = dataSourceFactory;
    }

}
Les annotations @JsonProperty("database") indique à Jackson les méthodes getter et setter à utiliser pour le parsing de la propriété database du fichier YAML de configuration.
La classe parente Configuration contient d'autres annotations pour le parsing des autres paramètres du fichier de configuration YAML : server, logging, metrics.

Classe principale

La classe principale étend la classe Application de Dropwizard qui prend en type générique la classe de configuration qui est ici HelloWorldConfiguration.

Deux méthodes de la classe Application sont à surcharger par la classe HelloWorldApplication de notre application :
  • initialize(Bootstrap b):
    • lecture des paramètres de configuration pour l'initialisation des composants de l'application:
      • Connexion à la base de données
      • Configuration Hibernate
  • run(Configuration c, Environnement e):
    • enregistrement des composants de l'application dans l'objet Environnement:
      • services REST
      • DAO
      • etc.
Extrait de la classe HelloWorldApplication :
public class HelloWorldApplication extends Application {

    public static void main(String[] args) throws Exception {
        new HelloWorldApplication().run(args);
    }

    // initialisation des composants de l'application à partir des paramètres de la configuration
    public void initialize(Bootstrap bootstrap) {
        (...)
        bootstrap.addBundle(hibernateBundle);
    }

    // enregistrement des composants de l'application dans l'objet Environnement
    public void run(HelloWorldConfiguration configuration, Environment environment) {
        final PersonDAO dao = new PersonDAO(hibernateBundle.getSessionFactory());
        final Template template = configuration.buildTemplate();
        environment.jersey().register(new PeopleResource(dao));
        environment.jersey().register(new PersonResource(dao));
        environment.jersey().register(new HelloWorldResource(template));
    }

}

Domaine métier

Les classes du domaine métier sont des POJOs et sont persistés par les DAO
Voici l'exemple d'un objet persisté par Hibernate :
@Entity
@Table(name = "people")
@NamedQueries({
        @NamedQuery(
                name = "com.example.helloworld.core.Person.findAll",
                query = "SELECT p FROM Person p"
        )
})
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "fullName", nullable = false)
    private String fullName;

    @Column(name = "jobTitle", nullable = false)
    private String jobTitle;
...
}
Les annotations ne sont pas obligatoires et dépendent du framework de persistence utilisé.
Dropwizard n'apporte pas de restriction sur le choix de la librairie utilisée et apporte par défaut JDBI (module dropwizard-jdbi) et Hibernate (module dropwizard-hibernate).

DAO

La DAO PersonDAO étend la classe abstraite AbstractDAO de Dropwizard pour avoir accès à Hibernate au travers de ces méthodes.

public class PersonDAO extends AbstractDAO {
    public PersonDAO(SessionFactory factory) {
        super(factory);
    }

    public Optional findById(Long id) {
        return Optional.fromNullable(get(id));
    }

    public Person create(Person person) {
        return persist(person);
    }

    public List findAll() {
        return list(namedQuery("com.example.helloworld.core.Person.findAll"));
    }
}

Service REST

La classe du service REST est autonome: il n'y a pas d'adhérence à Dropwizard.

Elle utilise directement les annotations de Jersey: @Path, @Produces, @GET et quelques annotations de Dropwizard : UnitOfWork, LongParam.

@Path("/people/{personId}")
@Produces(MediaType.APPLICATION_JSON)
public class PersonResource {

    private final PersonDAO peopleDAO;

    public PersonResource(PersonDAO peopleDAO) {
        this.peopleDAO = peopleDAO;
    }

    @GET
    @UnitOfWork
    public Person getPerson(@PathParam("personId") LongParam personId) {
        return findSafely(personId.get());
    }

    @GET
    @Path("/view_freemarker")
    @UnitOfWork
    @Produces(MediaType.TEXT_HTML)
    public PersonView getPersonViewFreemarker(@PathParam("personId") LongParam personId) {
        return new PersonView(PersonView.Template.FREEMARKER, findSafely(personId.get()));
    }
...
}

La DAO PersonDAO est injectée via le constructeur. Cela a été effectuée dans la méthode run de la classe principale de l'application HelloWorldApplication via la ligne :

environment.jersey().register(new PeopleResource(dao));

Documentation

Pour aller plus loin, voici les liens vers la documentation de Dropwizard :

Conclusion

Dropwizard est intéressant pour la mise en place de petites applications autonomes et pour la mise en oeuvre de micro services.

Il est à comparer avec Spring Boot sachant que de base Dropwizard ne repose pas sur Spring.

Références



A comparer avec Spring Boot :