Dropwizard series: clase Configuration

Comienzo otra serie de post, esta vez sobre Dropwizard, para el que ya hice un poc en un post previo. En este caso explicaré como cargar fácilmente un archivo yaml como configuración en Dropwizard, directamente en su clase Configuration, y como usarlo.

Concepto

Como vimos en el anterior post sobre Dropwizard, montar un proyecto con él consta de unos pocos pasos, entre ellos tener una clase Application, que es la clase principal que arranca nuestro servidor. Esta clase debe estar parametrizada con la clase de configuración Configuration. En el ejemplo anterior la dejamos vacía, pero en este caso vamos a ver como añadir fácilmente parámetros de configuración a nuestra aplicación.

Vamos a añadirle un par de parámetros de configuración a nuestro poc, los cuales usaremos desde nuestro resource hello-world.

Entorno usado:
Java JDK 1.8
Maven 3.3.9
Git 2.6.3
IDE Intellij 2016.1.3 Ultimate version

Pasos:

1. Usaremos como base el mismo proyecto del anterior post de dropwizard, podéis descargarlo de aquí.

2. Añadimos en nuestra clase de configuración dos atributos nuevos:

package com.edwise.pocs.dropwizardseries.configurationclass.config;

// imports...

public class DropwizardConfig extends Configuration {

  @NotEmpty
  private String host;

  private String defaultName;

  public String getHost() {
  return host;
  }

  public void setHost(String host) {
  this.host = host;
  }

  public String getDefaultName() {
  return defaultName;
  }

  public void setDefaultName(String defaultName) {
  this.defaultName = defaultName;
  }
}

Tendremos 2 parámetros, uno con el host y otro con un nombre por defecto para el mensaje de «hello world».
Como vemos. para añadir parámetros a nuestra configuración basta con añadir atributos en nuestra clase Configuration, y crearle sus getters y setters. Jackson se encarga del parseo, de manera trasparente.
En el caso del host, por ejemplo, hemos puesto que sea un parámetro obligatorio (con el @NotEmpty de hibernate validations). Si nuestra configuración no tiene valor para ese parámetro, dará error el arranque.

3. Añadimos nuestro yaml con los dos parámetros de configuración, bajo el directorio ‘src/main/resources’ (puede ser en cualquier otro directorio, no es obligatorio):

host: localhost
defaultName: Nobody

Con esto ya automáticamente dropwizard cargará esos datos en nuestra clase Configuration.

4. Ahora vamos a usar los parámetros desde la aplicación: en nuestro MessageResource le añadimos un constructor que reciba esos dos datos, y cambiamos el formato del mensaje que devolvemos:

package com.edwise.pocs.dropwizardseries.configurationclass.resource;

import com.edwise.pocs.dropwizardseries.configurationclass.model.Message;

// imports...

@Path("/hello-world")
@Produces(MediaType.APPLICATION_JSON)
public class MessageResource {
  private static final String HELLO_WORLD_SENTENCE = "Hello world %s, from %s!";

  private final String host;
  private final String defaultName;

  public MessageResource(String host, String defaultName) {
  this.host = host;
  this.defaultName = defaultName;
  }

  @GET
  public Message getHelloWorldMessage(@QueryParam("name") String name) {
  return new Message(String.format(HELLO_WORLD_SENTENCE,
  name != null ? name : defaultName, host));
  }
}

En el mensaje mostraremos tanto el nombre recibido (o el nombre por defecto si no) como el host.

5. En nuestra clase Application de dropwizard, modificamos el punto donde registramos el resource, pasándole los datos desde la clase Configuration:

package com.edwise.pocs.dropwizardseries.configurationclass;

// imports...

public class DropwizardApplication extends Application<DropwizardConfig> {

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

  @Override
  public void run(DropwizardConfig dropwizardConfig, Environment environment) {
  environment.jersey()
  .register(new MessageResource(dropwizardConfig.getHost(),
  dropwizardConfig.getDefaultName()));
  }
}

El objeto ‘dropwizardConfig’ recibido como parámetro en el método ‘run’ contiene ya los datos recogidos del yaml. Se los pasamos como parámetro al resource, y listo.

6. Solo falta arrancar y probar. Pero para que dropwizard procese el yaml, tenemos que pasárselo como parámetro al arrancar, de la siguiente manera:

java -jar target/dropwizard-series-configuration-class-1.0.0-SNAPSHOT.jar server src/main/resources/config.yaml

Si tenemos configurado un ‘Run configuration’ en Intellij para arrancarlo, también debemos añadir la ruta del yaml como segundo parámetro, de la misma manera.

7. Si ahora lanzamos una petición GET http://localhost:8080/hello-world contra nuestro servidor nos devolverá algo como esto:

{
  "message": "Hello world Nobody, from localhost!"
}

Todo el código de este post lo tenéis en mi github, proyecto dropwizard-series-configuration-class.

POC con Dropwizard

Desde que publiqué mi primer post en el blog, POC con Spring Boot, tenía pensado escribir uno similar con Dropwizard, y ahora que además lo estoy usando en mi actual proyecto, es el mejor momento para verlo 🙂

Concepto

Dropwizard es una especie de framework o librería para poder desarrollar rápidamente una aplicación web, muy al estilo de lo que nos aporta Spring Boot:
– Servidor «embebido», los famosos fat jars.
– Conjunto de librerías ya probadas y perfectamente compatibles entre sí.
– Simplicidad en la configuración.
Para mi la principal diferencia con Spring Boot, es que Dropwizard no usa ninguna librería de Spring. Su «stack» de tecnologias / librerías principales es la siguiente:
Maven
Jersey (implementación JAX-RS)
Jetty como servidor embebido.
Jackson, para todo el procesamiento y parseo de jsons.
Metrics, librería muy sencilla de usar para métricas, healtchecks, etc.
– Otras librerías secundarias: Guava, Logback, Hibernate Validator…

Vamos a ver un ejemplo de como montar desde cero un proyecto Dropwizard, con un endpoint «/helloworld».

Entorno usado:
Java JDK 1.8
Maven 3.3.9
Git 2.6.3
IDE Intellij 2016.1.2 Ultimate version

Pasos:

1. Crearemos un proyecto maven con el típico quick-start de maven (ver los 4 primeros pasos del post sobre Spring Boot). Yo lo he hecho directamente con mi archetype de maven.

2. Añadimos la dependencia principal de Dropwizard a nuestro pom:

    <dependency>
        <groupId>io.dropwizard</groupId>
        <artifactId>dropwizard-core</artifactId>
        <version>0.9.2</version>
    </dependency>

Con esa dependencia es suficiente. A su vez tiene todas las dependencias necesarias así como las librerías que comenté antes.

3. Para nuestro ejemplo tendremos el siguiente bean:

package com.edwise.pocs.dropwizard.model;

public class Message {

    private final String message;

    public Message(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

4. Que será lo que devolveremos en el único endpoint de nuestro Resource, que será así:

package com.edwise.pocs.dropwizard.resource;

// imports...

@Path("/hello-world")
@Produces(MediaType.APPLICATION_JSON)
public class MessageResource {
    private static final String HELLO_WORLD_SENTENCE = "Hello world %s!";
    private static final String DEFAULT_NAME = "Nobody";

    @GET
    public Message getHelloWorldMessage(@QueryParam("name") String name) {
        return new Message(
                String.format(HELLO_WORLD_SENTENCE, name != null ? name : DEFAULT_NAME)
        );
    }
}

Con las anotaciones básicas de un servicio REST JAX-RS. Lo único que hacemos es devolver un Message, con un mensaje construido a partir del nombre recibido como parámetro.
Con @Produces(MediaType.APPLICATION_JSON) el endpoint del resource devolverá un json. Jackson se encargará de manera trasparente de parsear la clase a json.

5. Creamos una clase de configuración. Dropwizard necesita que creemos una clase extendiendo de Configuration:

package com.edwise.pocs.dropwizard.config;

import io.dropwizard.Configuration;

public class DropwizardConfig extends Configuration {
}

En nuestro caso no añadiremos nada a la clase, dado que el ejemplo quiero que sea lo más simple posible, pero lo normal es añadir aquí cualquier parámetro de configuración necesario para el proyecto. En un próximo post seguramente lo veamos.

6. Y creamos nuestra clase ‘main’. Es muy similar a Spring Boot:

package com.edwise.pocs.dropwizard;

// imports...

public class DropwizardApplication extends Application<DropwizardConfig> {

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

    @Override
    public void run(DropwizardConfig dropwizardConfig, Environment environment) {
        environment.jersey().register(new MessageResource());
    }
}

Tenemos que extender la clase Application y parametrizandola con nuestra clase de configuración.
Además, es necesario implementar el método ‘run’, y en este caso lo único que hacemos en él es registrar en el entorno nuestro resource como componente Jersey. Como vemos, si necesitáramos usar algún parámetro de configuración al crear nuestros componentes aquí, podríamos hacerlo ya que el método ‘run’ recibe la clase de configuración como parámetro.

7. Ya solo nos falta añadir dos plugins a nuestro pom.xml de maven, son los siguientes:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.4.3</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>com.edwise.pocs.dropwizard.DropwizardApplication</mainClass>
                        </transformer>
                    </transformers>
                </configuration>
            </execution>
        </executions>
    </plugin>

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.0.0</version>
        <configuration>
            <archive>
                <manifest>
                    <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                </manifest>
            </archive>
        </configuration>
    </plugin>

El maven-shade-plugin se encarga de que el jar generado sea un «fat jar», o jar con todas las librerías necesarias para ejecutarse solo. En el plugin le indicamos nuestra clase ‘main’.
El maven-jar-plugin se suele usar para la generación del nombre del jar, según versiones y demás.

8. Vamos a arrancar ya nuestro proyecto dropwizard. Podemos hacerlo directamente desde una consola desde la raiz del proyecto ejecutando lo siguiente:

java -jar target/dropwizard-example-1.0.0-SNAPSHOT.jar server

O en Intellij podemos crear fácilmente un «Run configuration» de tipo Application con nuestra clase DropwizardApplication como MainClass y «server» como Program Arguments:

ScreenClip

Si ahora accedemos a http://localhost:8080/hello-world?name=edwise el servicio nos devolverá algo como esto:

{"message": "Hello world edwise!"}

Y aquí terminamos nuestro pequeño POC con Dropwizard. Quizá en próximos post explique alguna otra característica sobre Dropwizard.
Tenéis como siempre el proyecto completo en mi github, con el nombre dropwizard-example.