Microservicio RESTful con Spark Java

En esta ocasión vamos a ver cómo crear un microservicio RESTful utilizando el framework Spark de Java para su desarrollo, y utilizando objetos JSON para transferir la información. De esta forma la tecnología que usaremos será:

  • Apache Maven: Software para la gestión del proyecto, que incluye la gestión de dependencias y el arranque del microservicio, entre otras cosas.
  • Spark Java: Framework de desarrollo pequeño y ligero inspirado en Sinatra para el desarrollo de aplicaciones web en Java 8.
  • Gson: Biblioteca Java de Google que permite convertir objetos Java a JSON y viceversa sin necesidad de anotaciones.

Nota: En este tutorial se da por sentado que ya se tiene instalado y configurado en el equipo de desarrollo Apache Maven y el JDK de Java 8.

Primer paso: Crear el proyecto y configurar el fichero pom.xml

Lo primero que vamos a hacer es crear el proyecto utilizando Maven. Para ello ejecutamos el siguiente comando Maven en el directorio que contendrá el proyecto:

mvn archetype:generate -DgroupId=es.franl2p -DartifactId=RestfulApi -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Una vez que Maven termine de generar el proyecto, para lo cual necesitará descargarse varias bibliotecas, tendremos que modificar el fichero pom.xml para incluir las dependencias de Spark y de Gson.

 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>es.franl2p</groupId>
    <artifactId>RestfulApi</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>

    <name>RestfulApi</name>

    <url>http://maven.apache.org</url>

    <properties>
        <java.version>1.8</java.version>
        <spark.version>2.3</spark.version>
        <gson.version>2.3.1</gson.version>
        <junit.version>4.12</junit.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.sparkjava</groupId>
            <artifactId>spark-core</artifactId>
            <version>${spark.version}</version>
        </dependency>

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>${gson.version}</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <!-- Configure maven-compiler-plugin version. -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>es.franl2p.App</mainClass>
                    <arguments>
                    </arguments>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Segundo paso: Modelo de datos y servicios

Para nuestro microservicio RESTful vamos necesitar un pequeño modelo de datos con el que almacenar la información que se va a enviar y recibir. Para ello crearemos en primer lugar la clase User en el paquete es.franl2p.model con el siguiente código:

package es.franl2p.model;

public class User {
    private String id;
    private String name;
    private String email;
    
    /**
     * @param id
     * @param name
     * @param email
     */
    public User(String id, String name, String email) {
        super();
        this.id = id;
        this.name = name;
        this.email = email;
    }

    /**
     * @return the id
     */
    public String getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(String id) {
        this.id = id;
    }

    // Resto de getters y setters
    ...
}

Una vez creado el modelo de datos debemos desarrollar el servicio encargado de procesar las peticiones del microservicio y realizar las operaciones sobre el modelo de datos. Para ello crearemos la clase UserService dentro del paquete es.franl2p.services con el siguiente código:

package es.franl2p.services;

import java.util.LinkedList;
import java.util.List;

import es.franl2p.model.User;

public class UserService {
    /**
     * Lista de usuarios para pruebas
     */
    private static List<User> users;

    /**
     * Contador para el id de los nuevos usuarios
     */
    private static int contador = 0;
    
    /**
     * Contructor sin parámetros
     */
    public UserService() {
        // Carga de usuarios de pruebas
        users = new LinkedList<User>();
        users.add(new User("001", "Periquito", "perico@correo.es"));
        users.add(new User("007", "James Bond", "james.bond@007.com"));
    }
    
    /**
     * Recupera la lista completa de usuarios.
     * @return lista de usuarios.
     */
    public List<User> getAllUsers() {
        //Recupera la lista de usuarios cargada en el constructor
        return users;
    }

    /**
     * Recupera el usuario con el id dado.
     * @param id identificador del usuario.
     * @return Usuario encontrado o nulo en caso contrario.
     */
    public User getUser(String id) {
        User salida = null;

        for (User user : users) {
            if (user.getId().equals(id)) {
                salida = user;
            }
        }

        return salida;
    }

    /**
     * Crea un usuario con los datos dados. El id se autocalcula.
     * @param name nombre del usuario.
     * @param email email del usuario.
     * @return Usuario creado.
     */
    public User createUser(String name, String email) {
        contador++;
        User user = new User(contador + "", name, email);
        users.add(user);
        return user;
    }

    /**
     * Actualiza los datos de usuario con el id dado.
     * @param id identificador del usuario.
     * @param name nombre del usuario.
     * @param email email del usuario.
     * @return Usuario con los datos modificados.
     */
    public User updateUser(String id, String name, String email) {
        User salida = null;

        for (User user : users) {
            if (user.getId().equals(id)) {
                user.setName(name);
                user.setEmail(email);
                salida = user;
            }
        }

        return salida;
    }

}

Nota: En este código el constructor carga la lista de los usuarios. Normalmente estos datos están en algún tipo de almacenamiento persistente (como una base de datos) pero para simplificar el ejemplo vamos a crear a los usuarios directamente en el servicio.

Tercer paso: Controlador con Spark

Seguidamente necesitaremos un controlador que se encargue de gestionar las peticiones (GET, POST, PUT y DELETE) del API RESTful. Para ello vamos a crear la clase UserController dentro del paquete es.franl2p.controllers con el siguiente código:

package es.franl2p.controllers;

import static spark.Spark.get;

import es.franl2p.services.UserService;

public class UserController {
    public UserController(final UserService userService) {
        // Método para tratar los gets de /users
        get("/users", (request, response) -> userService.getAllUsers());
    }
}

Analizando el código del controlador vemos que de momento sólo contiene un constructor, al que se le pasa el userService, y que este contructor invoca al método get con dos parámetros, lo que se traduce en que:

  1. Al invocarse el método get indicamos que se capturen las peticiones GET para la ruta dada.
  2. El parámetro “/users” indica cual es la ruta de la aplicación que se debe capturar y tratar en este get. Cualquier otra ruta no será tratada por este método.
  3. El siguiente parámetro es en realidad una expresión lambda que se encargará de procesar la petición capturada y devolver un resultado. En este casoo la expresión lambda toma como parámetros la petición (request) y la respuesta (response), y ejecuta el método getAllUsers() del servicio userService, retornando el resultado del mismo.

De esta manera cuando se abra en un navegador la ruta “/users” la aplicación mostrará una página web con la lista completa de usuarios.

Cuarto paso: Terminar y probar la aplicación

Por último vamos a crear una clase Java llamada App en el paquete es.franl2p, la cual se usará para arrancar la aplicación e inicializar el controlador y el servicio de usuarios. El código de esta clase es el siguiente:

package es.franl2p;

import es.franl2p.controllers.UserController;
import es.franl2p.services.UserService;

public class App {
    public static void main(String[] args) {
        new UserController(new UserService());
    }
}

Vamos a comprobar que la aplicación hace realmente lo que hemos programado, así que tendremos que arrancarla utilizando el siguiente comando Maven:

mvn compile exec:java

Una vez arrancada abrimos un navegador cualquiera, por ejemplo Mozilla Firefox, y abrimos la siguiente URL:

http://localhost:4567/users

Y tendremos que ver una página  como esta:

RESTfull_users

Quinto paso: Retornar objetos JSON

La aplicación de momento está funcionando bien, sin embargo el resultado que está obteniendo no es fácilmente legible, ya que se están retornando objetos java convertidos a String.

Para que el resultado sea más amigable e interpretable por otra aplicación que consuma estos servicios vamos a retornar objetos JSON en lugar de los objetos Java actuales.

Lo primero que vamos a hacer es crear una clase que implemente la interfaz ResponseTransformer de Spark. Este ResponseTransformer se puede incluir en los métodos de las rutas para transformar los resultados de las peticiones tratadas. En nuestro caso crearemos la clase JsonTransformer, dentro del paquete es.franl2p.utils, que simplemente tomará los datos de la respuesta y los transformará a JSON:

package es.franl2p.utils;

import com.google.gson.Gson;
import spark.ResponseTransformer;

public class JsonTransformer implements ResponseTransformer {

    @Override
    public String render(Object data) throws Exception {
        return new Gson().toJson(data);
    }
}

Para usarlo basta con ponerlo como segundo parámetro en las llamadas a los métodos de Spark, que en este caso sería el método get de la clase UserController y que quedaría así:

public UserController(final UserService userService) {
        // Método para tratar los gets de /users
        get("/users", (request, response) -> userService.getAllUsers(), new JsonTransformer());

Si ahora volvemos a compilar, arrancar y probar la aplicación podremos ver que el resultado es un objeto JSON.

RESTfull_users

Sin embargo queda un último paso por hacer, y es que aunque el cuerpo de la respuesta sea un objeto JSON, si nos fijamos en la cabecera (headers) podemos ver que se está mandando con el Content-Type “text/html; charset=utf-8”, lo que identifica la respuesta como un html en lugar de JSON.

RESTfull_users_JSON_HTML

Último paso: Modificar la cabecera de las respuestas.

Para añadir en la cabecera de la respuesta el valor que la respuesta del servicio podríamos modificar el Content-Type del response directamente en el método get. Esta solución estaría bien si tenemos mapeadas pocas rutas con Spark o si queremos manejar el Content-Type de la respuesta de cada método de manera independiente, sin embargo en nuestro caso lo que tenemos es un microservicio que sólo va a responder objetos JSON, así que vamos a utilizar el método after de Spark para modificar el Content-Type de una manera más sencilla y centralizada para todo el microservicio.

El método de Spark after se invoca siempre después de procesar una ruta, de manera que usando este método podemos hacer todas las modificaciones que queramos sobre los objetos request y response. En nuestro caso bastará con tomar el objeto response y modificar su tipo con el siguiente código:

        // Filtro para convertir la salida a formato JSON
        after((request, response) -> {
            response.type("application/json");
        });

Si reiniciamos la aplicación y volvemos a probarla veremos que ahora el Content-Type sí se corresponde con el cuerpo de la respuesta, por lo que nuestro servicio RESTful ya estaría listo para ser usado por otra aplicación.

RESTfull_users_JSON

De momento nuestro microservicio es muy simple, ya que sólo tiene un método que lista todos los usuarios, así que más adelante haré una segunda parte de este tutorial en el que se incluirán rutas con parámetros y el resto de tipos de rutas (POST, PUT y DELETE).

Finalmente os podeis descarga el código completo del proyecto en mi repositorio de GitHub: https://github.com/flparedes/RestfulApi

Ya tenéis disponible la segunda parte aquí.

Anuncios

3 comentarios sobre “Microservicio RESTful con Spark Java

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