Web services REST con Dropwizard en Español !


Introducción

Dropwizard es un framework ligero para el desarrollo de aplicaciones que esta creciendo mucho en los últimos meses, es la competencia directa de frameworks como Spring boot, cuenta con soporte para configuraciones, métricas, manejo de logs y herramientas de producción nuevas y estables en el mercado de una forma fácil de utilizar, bien documentada y robusta. Es importante mencionar que Dropwizard a diferencia de muchos frameworks esta enfocada al 100% al desarrollo de aplicaciones REST.

Arquitectura

Dropwizard contiene los siguientes frameworks como parte de su arquitectura base:

  • Jetty: Las aplicaciones desarrolladas con Dropwizard contienen un contenedor Jetty embebido, lo que significa que las aplicaciones desarrolladas generarán lo que se conoce como un Fat Jar(Jar gordo) llamado así porque incluye entre sus dependencias el contenedor.
  • Jersey: Con el fin de desarrollar aplicaciones REST Dropwizard utiliza Jersey (Como implementación de JAX-RS) .
  • Jackson:Jackson se ha convertido en el estándar para serializar y desserializar de objetos JSON a objetos Java.
  • Metrics:La biblioteca de Metrics permite generar métricas sobre componentes críticos de la aplicación.
  • Otras herramientas: A demás de las mencionadas anteriormente, Dropwizard cuenta con soporte para trabajar con otros API’s como:
    • Guava
    • Logback y Slf4j
    • Hibernate
    • Apache HttpClient
    • JDBI
    • Liquibase
    • Freemaker
    • JodaTime

Configuración

Con el fin de configurar Dropwizard se debe agregar la siguiente dependencia al proyecto:

<properties>
    <dropwizard.version>1.0.2</dropwizard.version>
</properties>
<dependencies>
    <dependency>
        <groupId>io.dropwizard</groupId>
        <artifactId>dropwizard-core</artifactId>
        <version>${dropwizard.version}</version>
    </dependency>
</dependencies>

A demás de los siguientes plugins :

<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-shade-plugin</artifactId>
			<version>2.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.raidentrance.DropwizardApplication</mainClass>
							</transformer>
						</transformers>
					</configuration>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>

Creando clase de Configuración de Dropwizard

En Dropwizard se trabajará con un archivo de configuración yml, el cuál contendrá información sobre puertos a utilizar, configuraciones de base de datos, manejo de logs, configuraciones propias, etc. La forma en la que se cargan esas configuraciones a la aplicación es a través del uso de una clase de configuración que toma las configuraciones de un archivo yml, para este ejemplo se utilizará el siguiente archivo application.yml:

appName: raidentrance
server:
  applicationConnectors:
    - type: http
      port: 8080

Como se puede observar se cuentan con diferentes propiedades para ver la lista completa de propiedades disponibles hacer click aqui. Adicionalmente se agregó la propiedad appName, esta propiedad es un ejemplo que muestra como agregar configuraciones propias. Para cargar esa configuración a nuestra aplicación se debe crear una clase de Configuración, en este caso se utilizará la clase DropwizardConfiguration.java :

/**
 *
 */
package com.raidentrance.config;

import org.hibernate.validator.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;

/**
 * @author raidentrance
 *
 */
public class DropwizardConfiguration extends Configuration {
	@NotEmpty
	private String appName;

	@JsonProperty
	public String getAppName() {
		return appName;
	}
	@JsonProperty
	public void setAppName(String appName) {
		this.appName = appName;
	}
}

Como se puede observar en la clase de configuración DropwizardConfiguration se define la nueva propiedad appName que contendrá el valor asignado en el archivo application.yml, las demás propiedades son mapeadas en campos de la clase padre Configuration.java.

Creando modelo de la aplicación

En este ejemplo se desarrollará un web service REST que devuelva una lista de usuarios, para conseguir esto se definirán los siguientes pojos:

package com.raidentrance.model;

/**
 * @author raidentrance
 *
 */
public class Role {
	private String name;
	private String description;

	public Role() {
	}

	public Role(String name, String description) {
		super();
		this.name = name;
		this.description = description;
	}
	.....
}
package com.raidentrance.model;

import com.fasterxml.jackson.annotation.JsonView;

/**
 * @author raidentrance
 *
 */
public class User {
	@JsonView(Views.Public.class)
	private String username;
	@JsonView(Views.Internal.class)
	private String password;
	@JsonView(Views.Public.class)
	private Role role;

	public User() {
	}

	public User(String username, String password, Role role) {
		super();
		this.username = username;
		this.password = password;
		this.role = role;
	}

	.....
}
/**
 *
 */
package com.raidentrance.model;

/**
 * @author raidentrance
 *
 */
public class Views {
	public static class Public {
	}
	public static class Internal extends Public {
	}
}

Las clases User y Role son Pojos que se utilizarán para serializar y des serializar objetos.

La clase Views será utilizada para devolver diferentes tipos de objetos JSON dependiendo del endpoint que se consulte, por ejemplo un endpoint para obtener los usuarios no debe devolver el password, pero un endpoint que recibe un usuario para crearlo si lo debe recibir para estos enpoints se utilizará una vista diferente.

Obteniendo información

Para obtener información de una fuente externa es posible utilizar JDBI o Hibernate, en este ejemplo no se utilizará ninguna de las dos debido a que ese ejemplo se explicará más adelante en un ejemplo separado. En este ejemplo se utilizará una clase auxiliar llamada SourceHelper.java que devolverá una lista pequeña con información.

/**
 *
 */
package com.raidentrance.data;

import java.util.ArrayList;
import java.util.List;
import com.raidentrance.model.Role;
import com.raidentrance.model.User;

/**
 * @author raidentrance
 *
 */
public class SourceHelper {
	public List<User> users = new ArrayList<>();

	public SourceHelper() {
		loadSampleData();
	}

	private void loadSampleData() {
		users.add(new User("raidentrance", "superSecret", new Role("ADMIN", "Administrator of the system")));
		users.add(new User("admin", "superSecret", new Role("USER", "Normal user in the system")));
	}

	public List<User> getUsers() {
		return users;
	}
}

Creando el servicio REST

A continuación se presenta el servicio REST utilizado, en este caso contendrá el endpoint GET “/users” el cual devolverá una lista de usuarios.

/**
 *
 */
package com.raidentrance.rest;

import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonView;
import com.raidentrance.data.SourceHelper;
import com.raidentrance.model.User;
import com.raidentrance.model.Views;

/**
 * @author raidentrance
 *
 */
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

	private SourceHelper helper = new SourceHelper();

	private static final Logger LOG = LoggerFactory.getLogger(UserResource.class);

	@GET
	@JsonView(Views.Public.class)
	public Response getUsers() {
		LOG.info("Getting all users");
		List<User> users = helper.getUsers();
		return Response.ok(users).build();
	}

}

A continuación se explican las anotaciones utilizadas:

  • @Path(“/users”)   : Endpoint base a utilizar
  • @Produces(MediaType.APPLICATION_JSON) : Significa que el endpoint devolverá objetos tipo JSON
  • @GET :El endpoint será accedido a través del método HTTP GET
  • @JsonView(Views.Public.class) : Significa que este endpoint solo devolverá los atributos anotados con la vista Public, en este ejemplo se puede observar en la clase User.java que el atributo password no será devuelto.

Creando la clase de aplicación

La clase de aplicación es la más importante en Dropwizard ya que será la responsable de iniciar la aplicación y configurar los web services utilizados.

/**
 *
 */
package com.raidentrance;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.raidentrance.config.DropwizardConfiguration;
import com.raidentrance.rest.UserResource;

import io.dropwizard.Application;
import io.dropwizard.setup.Environment;

/**
 * @author raidentrance
 *
 */
public class DropwizardApplication extends Application<DropwizardConfiguration> {

	private static final Logger LOG = LoggerFactory.getLogger(DropwizardApplication.class);

	@Override
	public void run(DropwizardConfiguration configuration, Environment environment) throws Exception {
		LOG.info("Starting app {}", configuration.getAppName());
		LOG.info("Registering resources");
		final UserResource resource = new UserResource();
		environment.jersey().register(resource);
		LOG.info("Resources registered");
	}

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

Como se puede observar la clase aplicación recibe como genérico el nombre de la clase de configuración ya que con esta configurará la aplicación para poder iniciarla de forma correcta.

La línea LOG.info(“Starting app {}”, configuration.getAppName()); imprimirá el valor que definimos en el archivo yml en la propiedad appName, de este modo es posible definir configuraciones propias.

Otro punto importante a mencionar es que es en esta clase donde se registran los web services que va a utilizar la aplicación, para hacerlo se usan las siguientes líneas:

final UserResource resource = new UserResource(); environment.jersey().register(resource);

Ejecutando la aplicación

Para ejecutar la aplicación se debe compilar con maven a través del comando mvn clean install acceder al folder target y ejecutarlo como se muestra:

java -jar dropwizard-example-0.0.1-SNAPSHOT.jar server ../src/main/resources/application.yml 

Donde :

  • dropwizard-example-0.0.1-SNAPSHOT.jar : jar que contiene la aplicación y el server embebido.
  • server: Ejecuta la aplicación como un HTTP server.
  • ../src/main/resources/application.yml:Archivo yml de configuración, como se puede observar si en un futuro se quiere cambiar es posible arrancar la aplicación utilizando un yml diferente utilizando el la misma aplicación sin necesidad de recompilarla.

Una vez que se ejecuta la aplicación la salida será la siguiente:

INFO  [2016-09-27 18:59:04,533] org.eclipse.jetty.util.log: Logging initialized @1003ms
INFO  [2016-09-27 18:59:04,594] io.dropwizard.server.DefaultServerFactory: Registering jersey handler with root path prefix: /
INFO  [2016-09-27 18:59:04,594] io.dropwizard.server.DefaultServerFactory: Registering admin handler with root path prefix: /
INFO  [2016-09-27 18:59:04,594] com.raidentrance.DropwizardApplication: Starting app raidentrance
INFO  [2016-09-27 18:59:04,594] com.raidentrance.DropwizardApplication: Registering resources
INFO  [2016-09-27 18:59:04,597] com.raidentrance.DropwizardApplication: Resources registered
INFO  [2016-09-27 18:59:04,597] io.dropwizard.server.DefaultServerFactory: Registering jersey handler with root path prefix: /
INFO  [2016-09-27 18:59:04,597] io.dropwizard.server.DefaultServerFactory: Registering admin handler with root path prefix: /
INFO  [2016-09-27 18:59:04,598] io.dropwizard.server.ServerFactory: Starting DropwizardApplication
INFO  [2016-09-27 18:59:04,687] org.eclipse.jetty.setuid.SetUIDListener: Opened application@7555b920{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
INFO  [2016-09-27 18:59:04,687] org.eclipse.jetty.setuid.SetUIDListener: Opened admin@4152d38d{HTTP/1.1,[http/1.1]}{0.0.0.0:8081}
INFO  [2016-09-27 18:59:04,689] org.eclipse.jetty.server.Server: jetty-9.3.9.v20160517
INFO  [2016-09-27 18:59:05,104] io.dropwizard.jersey.DropwizardResourceConfig: The following paths were found for the configured resources:

    GET     /users (com.raidentrance.rest.UserResource)

INFO  [2016-09-27 18:59:05,106] org.eclipse.jetty.server.handler.ContextHandler: Started i.d.j.MutableServletContextHandler@1cb7936c{/,null,AVAILABLE}
INFO  [2016-09-27 18:59:05,111] io.dropwizard.setup.AdminEnvironment: tasks = 

    POST    /tasks/log-level (io.dropwizard.servlets.tasks.LogConfigurationTask)
    POST    /tasks/gc (io.dropwizard.servlets.tasks.GarbageCollectionTask)

WARN  [2016-09-27 18:59:05,111] 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.       !
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
INFO  [2016-09-27 18:59:05,116] org.eclipse.jetty.server.handler.ContextHandler: Started i.d.j.MutableServletContextHandler@19058533{/,null,AVAILABLE}
INFO  [2016-09-27 18:59:05,125] org.eclipse.jetty.server.AbstractConnector: Started application@7555b920{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
INFO  [2016-09-27 18:59:05,126] org.eclipse.jetty.server.AbstractConnector: Started admin@4152d38d{HTTP/1.1,[http/1.1]}{0.0.0.0:8081}
INFO  [2016-09-27 18:59:05,126] org.eclipse.jetty.server.Server: Started @1597ms
INFO  [2016-09-27 18:59:13,735] com.raidentrance.rest.UserResource: Getting all users
0:0:0:0:0:0:0:1 - - [27/sep/2016:18:59:13 +0000] "GET /users HTTP/1.1" 200 183 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36" 70
INFO  [2016-09-27 18:59:46,577] com.raidentrance.rest.UserResource: Getting all users
0:0:0:0:0:0:0:1 - - [27/sep/2016:18:59:46 +0000] "GET /users HTTP/1.1" 200 183 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36" 2

Para ejecutar la petición se debe hacer uso de un cliente http a http://localhost:8080/users via GET y se obtendrá como resultado lo siguiente:

[
{
username: "raidentrance",
role: {
name: "ADMIN",
description: "Administrator of the system"
}
},
{
username: "admin",
role: {
name: "USER",
description: "Normal user in the system"
}
}
]

Puedes encontrar el código completo del ejemplo en el siguiente enlace:

https://github.com/raidentrance/dropwizard-example

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s