Plugins de Maven más comunes en Español


En este post se explicará de forma simple los plugins más comunes en Maven.

Apache maven compiler plugin

El compiler plugin de apache es utilizado para compilar el código fuente del proyecto.

¿Por qué es importante?

  • La configuración default del código fuente es 1.5, de tal modo que si tu aplicación utiliza cualquier novedad de java 1.6 o superior el código fuente no compilará.
  • No importa si en las variable de entorno del equipo se utiliza java 1.8, si el maven compiler plugin no está definido, Maven utilizará java 1.5 para compilarlo.
  • Puedes utilizarlo para forzar a tu proyecto a utilizar una versión de Java a la que se tiene configurada en el equipo.

¿Qué errores puede producir?

multi-catch statement is not supported in -source 1.5 [ERROR] (use -source 7 or higher to enable multi-catch statement)
diamond operator is not supported in -source 1.5 (use -source 7 or higher to enable diamond operator)

¿Cómo utilizarlo?

<project>
  [...]
  <build>
    [...]
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
    [...]
  </build>
  [...]
</project>

Maven Jar plugin

El plugin provee la capacidad de construir Jars.

¿Por qué es importante?

  • Permite personalizar el archivo Manifest de la aplicación que se está generando
  • Permite incluir o excluir contenido del jar que se está generando
  • Permite hacer un Jar ejecutable, para esto se debe especificar la clase principal de la aplicación.

¿Qué errores puede producir?

no hay ningún atributo de manifiesto principal en application-1.jar

¿Cómo utilizarlo?

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-jar-plugin</artifactId>
	<configuration>
		<archive>
			<manifest>
				<addClasspath>true</addClasspath>
				<mainClass>com.raidentrance.MainClass</mainClass>
			</manifest>
		</archive>
	</configuration>
</plugin>

Maven assembly plugin

El assembly plugin fue creado para permitir a los usuarios crear un un jar con sus dependencias, módulos, documentación y otros archivos.

¿Por qué es importante?

  • Si se desea ejecutar un jar, se debe recordar que este requiere de todas sus dependencias.

¿Qué errores puede producir?

java.lang.NoClassDefFoundError

¿Cómo utilizarlo?

<plugin>
	<artifactId>maven-assembly-plugin</artifactId>
	<configuration>
		<descriptorRefs>
			<descriptorRef>jar-with-dependencies</descriptorRef>
		</descriptorRefs>
		<archive>
			<manifest>
				<mainClass>com.raidentrance.MainClass</mainClass>
			</manifest>
		</archive>
	</configuration>
	<executions>
		<execution>
			<id>make-assembly</id>
			<phase>package</phase>
			<goals>
				<goal>single</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Nota: en este plugin se puede definir de igual forma la clase principal que ejecutará la aplicación

Apache Maven dependency plugin

El dependency plugin provee la capacidad de manipular artefactos. Este puede ayudar a copiar y desempaquetar artifacts de repositorios locales y remotos a un lugar específico.

¿Por qué es importante?

  • Analiza las dependencias del proyecto y determinar cuales son : utilizadas y declaradas, utilizadas y no declaradas, no usadas y declaradas.

Ejemplo

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-dependency-plugin</artifactId>
	<executions>
		<execution>
			<id>analyze</id>
			<goals>
				<goal>analyze-only</goal>
			</goals>
			<configuration>
				<failOnWarning>true</failOnWarning>
			</configuration>
		</execution>
	</executions>
</plugin>

Salida de ejemplo:

[WARNING] Unused declared dependencies found:
[WARNING]    org.slf4j:slf4j-log4j12:jar:1.7.21:compile
[WARNING]    org.springframework:spring-beans:jar:4.0.3.RELEASE:compile
  • Copia las dependencias a un directorio diferente, un uso común es mover algunas dependencias a un directorio lib.

Ejemplo

<plugin>
	<artifactId>maven-dependency-plugin</artifactId>
	<executions>
		<execution>
			<phase>install</phase>
			<goals>
				<goal>copy-dependencies</goal>
			</goals>
			<configuration>
				<outputDirectory>${project.build.directory}/lib</outputDirectory>
			</configuration>
		</execution>
	</executions>
</plugin>
  • Descomprime el proyecto en un directorio específico a demás de incluir o excluir archivos.

Ejemplo

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-dependency-plugin</artifactId>
	<executions>
		<execution>
			<id>unpack-dependencies</id>
			<phase>package</phase>
			<goals>
				<goal>unpack-dependencies</goal>
			</goals>
			<configuration>
				<includes>**/*.class</includes>
				<excludes>**/*.properties</excludes>
				<outputDirectory>${project.build.directory}/alternateLocation</outputDirectory>
				<overWriteReleases>false</overWriteReleases>
				<overWriteSnapshots>true</overWriteSnapshots>
			</configuration>
		</execution>
	</executions>
</plugin>

Jetty plugin

El plugin de jetty es muy utilizado para un rápido desarrollo y pruebas durante el desarrollo de aplicaciones web que requieren un contenedor web.

¿Por qué es importante?

  • Es posible iniciar el contenedor de jetty utilizando un goal de maven mvn jetty:run.
  • Es posible desplegar los cambios realizados en el código sin la necesidad de reiniciar el contenedor.
  • Incrementa la productividad del equipo al realizar los hot deploys.

Ejemplo

<plugin>
	<groupId>org.eclipse.jetty</groupId>
	<artifactId>jetty-maven-plugin</artifactId>
	<version>9.2.11.v20150529</version>
</plugin>

Una vez colocado este plugin en la aplicación ejecutar el goal mvn jetty:run. Con esto se ejecutará el contenedor en el puerto 8080 utilizando el contexto ROOT.

Agregando un context path, intervalo de verificación y puerto.

<plugin>
	<groupId>org.eclipse.jetty</groupId>
	<artifactId>jetty-maven-plugin</artifactId>
	<configuration>
		<scanIntervalSeconds>10</scanIntervalSeconds>
		<webApp>
			<contextPath>/raidentrance</contextPath>
		</webApp>
		<httpConnector>
			<port>9999</port>
		</httpConnector>
	</configuration>
</plugin>

Una vez que se ejecute la aplicación el contenedor se desplegará en http://localhost:9999/raidentrance

Jacoco maven plugin

Este plugin es utilizado para generar el code coverage del proyecto.

¿Por qué es importante?

  • Permite conocer la cantidad de código cubierto por pruebas unitarias.
  • Permite conocer las condiciones evaluadas con test unitarios en pruebas unitarias.

Ejemplo

<plugin>
	<groupId>org.jacoco</groupId>
	<artifactId>jacoco-maven-plugin</artifactId>
	<configuration>
		<append>true</append>
	</configuration>
	<executions>
		<execution>
			<goals>
				<goal>prepare-agent</goal>
			</goals>
		</execution>
		<execution>
			<id>post-unit-test</id>
			<phase>test</phase>
			<goals>
				<goal>report</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Para generar los reportes ejecutar el goal de maven mvn site.

Los libros recomendados para este tema son:

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Buenas prácticas en el desarrollo de Web services REST


Hoy en día el uso de web services REST es indispensable en el desarrollo de aplicaciones, creación de API’s, etc. El problema llega cuando se comienzan a definir estos web services sin considerar buenas practicas. En este post se busca mencionar buenas prácticas a seguir en la definición de servicios web REST específicamente.

Ser stateless SIEMPRE

La primera regla al manejar servicios REST es que los servicios deben ser 100% STATELESS esto significa que cada petición que se realice debe ser independiente a la anterior sin ningún problema.

Una ventaja que se tiene con esto es al momento de crecer la aplicación, en lugar de hacer un cluster y realizar migración de sesiones, solo se debe colocar un balanceador de carga y una instancia más de la aplicación y listo ! al ser stateless no importa que server atienda la petición esta siempre será correcta.

Utilizar sustantivos, no verbos:

Una práctica común al definir los nombres de endpoints es utilizar verbos en el nombre del endpoint, como se muestra a continuación:

Método : GET      Endpoint: /getUsers/     Descripción:Devuelve una lista de usuarios

Se deben utilizar solo sustantivos en lugar de verbos, si se utilizan verbos en el nombre del endpoint se vuelve redundante, recordemos que el verbo ya está definido en el método HTTP utilizado.

La forma correcta de definir el endpoint anterior es la siguiente:

Método : GET      Endpoint: /users/     Descripción:Devuelve una lista de usuarios

En este ejemplo se cuenta con el método HTTP GET y el endpoint /users/ de este modo se entiende de forma clara y sin ser redundante que la operación es obtener una lista de usuarios.

En caso de que se desee obtener un solo usuario la forma correcta sería la siguiente:

Método : GET      Endpoint: /users/123    Descripción:Devuelve al usuario con el id especificado

En este caso se puede observar que tampoco es necesario utilizar ningún verbo en la definición del endpoint para definirlo de forma clara.

Ejemplos de definiciones erróneas de endpoints:

/getUsers        /updateUser     /deleteUser

Utiliza el método HTTP correcto

Un problema común en muchos API’s REST es que la mayoría de los endpoints definidos son GET y POST. A continuación se presentan los métodos HTTP disponibles, su uso y respuestas comunes para los mismos:

  • GET: Devuelve uno o muchos recursos dependiendo del endpoint consumido, el resultado común es un JSON con el objeto u objetos solicitados.
  • POST: Crea un nuevo recurso, una buena práctica es devolver el link al recurso creado, más detalle más adelante en la sección de HATEOAS.
  • PUT: Actualiza un recurso existente, una buena práctica es devolver el link al recurso actualizado, más detalle más adelante en la sección de HATEOAS.
  • PATCH: Actualiza de forma parcial un objeto existente, una buena práctica es devolver el link al recurso actualizado, más detalle más adelante en la sección de HATEOAS.
  • DELETE: Borra un recurso existente, una buena práctica es devolver un HTTP status 204 (Not content) para indicar que el recurso se borró de forma exitosa.

Más adelante se explicará de forma más detallada los estatus http comunes y en que casos se deben devolver.

Permitir sobre escritura de los métodos HTTP

Algunos clientes HTTP solo pueden trabajar utilizando GET y POST, por esto es necesario permitir sobre escritura de métodos,X-HTTP-Method-Override permite sobre escribir de POST a PUT, PATCH y DELETE. Esto solo es utilizado para cambiar el comportamiento de POST, GET nunca debe modificar recursos en el servidor.

Utilizar de forma correcta los métodos HTTP

Un problema común en API’s http es que siempre devuelven estatus http 200 para todas las peticiones sin importar si la petición se ejecutó de forma correcta o no y de igual modo cuando se produce un error se devuelve siempre un estatus 500 que no da mucha información, a continuación se presentan los rangos de uso de los estatus para tener un mejor conocimiento de como utilizarlos:

  1. Rangos de estatus HTTP:
    • 1xx (100-199) : Estatus de información
    • 2xx(200-299): Estados de éxito, en este rango podemos ver estatus que significan que la petición se ejecuto de forma exitosa. Por ejemplo 200 (OK) Significa una petición correcta, 201 (Not content)Significa que se borro un recurso de forma exitosa, etc.
    • 3xx(300-399):Re dirección, Significa que hace falta un paso extra para completar la petición y se realiza una re dirección para conseguirlo.
    • 4xx(400-499):Error del lado del cliente, un error común en muchos API’s es devolver siempre un estatus 400 para cualquier tipo de error que se genera. La forma correcta de hacerlo es identificar si el error es del lado del servidor o del lado del cliente, por ejemplo en este rango se pueden encontrar errores como 404(El cliente trata de acceder a un recurso que no existe), 403(El cliente no tiene suficientes permisos), etc.
    • 5xx(500-599): Error del lado del servidor, del mismo modo que en el caso anterior, nuestro API rest debe ser capaz de identificar en que lado se produce un error, un error del lado del servidor puede ser que una base de datos no esté disponible por ejemplo, no es un error del lado del cliente sino es un problema que se tiene con el servidor, para este caso se debe devolver un error en el rango de los 5xx.

Si cada petición devuelve diferentes estatus dependiendo de la situación, el cliente será capaz de presentar esos errores de forma correcta y simple.A continuación se muestra un ejemplo de como utilizar  estos estatus en un endpoint común:

Método : GET      Endpoint: /users/123    Descripción:Devuelve al usuario con el id especificado

Respuestas posibles:

  • HTTP 200 (El usuario existe y se cuentan con los permisos):JSON con los datos del usuario solicitado
  • HTTP 404 (En caso de que el recurso solicitado no exista):JSON con mensaje de error y código de mensaje
  • HTTP 403 (El usuario está autenticado pero no tiene suficientes permisos para acceder al recurso):JSON con mensaje de error y código de mensaje.

Utilizar HATEOAS(Hypermedia as the Engine of Application State)

Utilizar HATEOAS brinda una experiencia muy buena al momento de navegar a través del API ya que es posible revisar prácticamente todo el API sin necesidad de ver una página de documentación en el siguiente enlace se puede observar un ejemplo de como implementarlo utilizando Spring boot Spring Boot + REST Jersey (Agregando Spring Hateoas y Dto) Parte 3.

Siempre utiliza SSL para todas las peticiones

Muchos usuarios se conectan a sus aplicaciones utilizando redes públicas en la calle que pueden ser escuchadas por quien sea, con esto se vuelve obligatorio que todas las peticiones atendidas por los servicios se realicen a través de SSL.

Documentación del API

Documentar un API REST ya no es muy complicado ni fastidioso como lo fue hace algunos años, actualmente existen muchas herramientas para documentar API’s que permiten simular peticiones, status http e incluso que generan código de ejemplo en diferentes lenguajes para los clientes que consuman el servicio web. Una recomendación personal al respecto es el uso de https://apiary.io/.

Paginación

Existen endpoints que pueden devolver muchos registros, lo cual muchas veces se vuelve complicado de manejar ya que recordemos que muchas veces los clientes que utilizan los servicios son aplicaciones móviles con recursos limitados. Una buena forma de realizar esta paginación es como se muestra a continuación:

GET /tweets?offset=20&limit=10

Versionamiento del API

Nunca se debe desplegar un API sin versión ya que siempre existirán aplicaciones consumiendo los servicios y si se desea realizar alguna modificación es posible que se afecte a una aplicación que ya usa esos servicios, por esto cada versión que se libere debe contener en su contexto su número específico, evita utilizar 1.5 o 4.9.2.1, trata de ser simple y asígnalos como se muestra a continuación:

/application/api/v1

Autenticación

Un API REST debe ser Stateless, esto significa que no existirá una sesión entre el cliente y el servicio, por esto en cada petición se debe enviar el mecanismo de autenticación, un mecanismo común es utilizar OAuth 2 Tokens(Bearer Token) en el campo del Basic authentication, más info sobre ellos aquí. Sólo es importante recordar que todo esto depende de que siempre se utilice SSL  ya que el token viajará en cada petición.

Utiliza siempre un framework ligero

Hoy en día los servicios REST son muy utilizados en arquitecturas de microservicios donde solo se requiere que se ejecuten tareas simples y específicas. Por esto es recomendable trabajar sobre entornos ligeros que brinden una forma simple de despliegue de aplicaciones y una fácil desarrollo. Dos herramientas que personalmente recomiendo para trabajar con REST en Java específicamente son Spring Boot y Dropwizard.

Las prácticas planteadas en este post son independientes del leguaje ya que cualquier API REST escrito en cualquier lenguaje de programación las debe seguir.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com