Implementación de los servicios REST

JAX-RS es una API de Java que facilita la creación de servicios web de acuerdo con la arquitectura REST. JAX-RS utiliza anotaciones, introducidas desde Java SE 5, para simplificar el desarrollo y el despliegue de clientes de servicios web y de los servicios en sí mismos (endpoints). La Figura 1 ilustra de manera general los elementos que intervienen:

Figura 1

Las clases Recurso contienen las implementaciones correspondientes a cada verbo http (GET|POST|PUT|DELETE) asociadas con cada URI (path). Por ejemplo, podemos suponer que tenemos una clase recurso para definir todos los servicios que se ofrecen para el recurso Book.

Si un cliente HTTP invoca un servicio, por ejemplo POST /books, funciona de la siguiente manera:

  1. La peticiónPOST /books es procesada del lado del servidor por el core de JAX-RS.
  2. El core de JAX-RS identifica el "endpoint": el método asociado con la ruta /books y el método correspondiente al POST
  3. El método es ejecutado.
  4. Cuando finaliza la ejecución, la respuesta es transformada por JAX-RS para que sea conforme con el protocolo HTTP y enviada de regreso al cliente en la representación seleccionada por el desarrollador, por ejemplo JSON. La Figura 2 ilustra de una forma simplificada lo que sucede: en la petición viaja un objeto en formato JSON. Cuando la petición es procesada por JAX-RS, el método correspondiente con la petición recibe un objeto java, en la figura este objeto es una instancia de la clase BookDTO.

Figura 2

Mientras que el cliente de los servicios REST utiliza representaciones JSON de los recursos, la implementación de los recursos utiliza objetos de alguna clase java. Las instancias de esta clase son objetos que solo tienen los valores de los atributos. Estos se suelen llamar POJOs (Plain Old Java Object). Como estos objetos se transfieren por la red, por convención cada clase java que representa un recurso se llama: RecursoDTO donde Recurso es el nombre de recurso y la sigla DTO significa (Data Transfer Object. Por ejemplo, para el recurso Book tenemos la clase BookDTO.

Implementación de una clase recurso

Supongamos que queremos diseñar servicios rest para una aplicación que maneja información básica de ciudades: crear una ciudad, obtener todas las ciudades, obtener una ciudad dado su identificador, cambiar la información de una ciudad dado su identificador y borrar una ciudad dado su identificador.

El interés del ejemplo es mostrar cómo se implementan los servicios REST usando Jax-RS. La lógica de este ejemplo es un mock que sencillamente construye en memoria una lista con las ciudades.

Método Path Acción Parámetros Cuerpo Retorno
GET /cities Lista los registros de City (READ) Colección de registros de City
GET /cities/:id Obtiene los atributos de una instancia de City (READ) que tiene identificado id @PathParam id: Identificador del registro Atributos de la instancia de City
POST /cities Crear una nueva instancia de la entidad City (CREATE) Atributos de la instancia de City a crear Instancia de City creada, incluyendo su nuevo ID
PUT /cities/:id Actualiza una instancia de la entidad City (UPDATE) @PathParam id: Identificador del registro Objeto JSON de City Instancia de City actualizada
DELETE /cities/:id Borra instancia de City en el servidor (DELETE) @PathParam id: Identificador del registro

La clase CityResource tendrá entonces los siguientes métodos.

Método Descripción
List<CityDTO> getCities() Retorna la lista de ciudades
CityDTO createCity(CityDTO city) Crea una ciudad con la información enviada como parámetro
CityDTO getCity( Long id) Retorna la ciudad identificada con id
CityDTO updateCity(Long id, CityDTO city) Actualiza la inforación de la ciudad identificada con id
void deleteCity(Long id) Borra la ciudad identificada con id

Para que JAX-RS procese una clase como recurso, esta debe estar anotada con las anotaciones definidas en el API JAX-RS. Las anotaciones sirven para indicar: el path recurso, la correspondencia del método con el verbo de http, el tipo de representación que se transporta, la correspondencia entre la representación y la clase java que contiene los valores del recurso. Siguiendo con el ejemplo, los métodos anteriores deben tener las siguientes anotaciones para realizar la correspondencia con los verbos HTTP:

Anotaciones para indicar el verbo HTTP

Anotación Método Descripción
@GET List<CityDTO> getCities() Retorna la lista de ciudades
@POST CityDTO createCity(CityDTO city) Crea una ciudad con la información enviada como parámetro
@GET CityDTO getCity( Long id) Retorna la ciudad identificada con id
@PUT CityDTO updateCity(Long id, CityDTO city) Actualiza la información de la ciudad identificada con id con la información pasada por parámetro en city
@DELETE void deleteCity(Long id) Borra la ciudad identificada con id

Anotaciones para indicar el path

Cuando definimos en el API REST el servicio:

GET /cities

En este caso cities es el path. Cuando en una clase recurso todos los métodos tienen el mismo path inicial este path se puede anotar al comienzo de la clase:

   @Path("cities")
   public class CityResource {
    ....
   }

Cuando el path tiene más información que el del comienzo de la clase, por ejemplo:

GET /cities/id Donde id, en este ejemplo, es el identificador de la ciudad y es un valor numérico.

En este caso se debe definir:

    @GET
    @Path("{id: \\d+}")
    public CityDTO getCity(@PathParam("id") Long id) throws CityLogicException {
        ...
    }

Note que la expresión \\d+ es una expresión regular que significa que el path es un número (decimal). Note que el método debe recibir ese id como parámetro. El tipo del parámetro ha sido definido como Long y está anotado con @PathParam("id").

Si quisíeramos un servicio GET cities/bogota tendríamos que definir:

    @GET
    @Path("{name: [a-zA-Z][a-zA-Z]*}}")
    public CityDTO getCitybyName(@PathParam("name") String name) throws CityLogicException {
        ...
    }

En este caso la expresión regular indica que son letras (mayúsculas y minúsculas) y el tipo del parámetro es String.

Si por ejemplo quisiéramos que el valor que estamos

Anotaciones para indicar el tipo de representación que se transmite

Para indicar la representación que produce o consume el servicio se utiliza las anotaciones @Produces y @Consumes. Por ejemplo, en el siguiente código se indica que la representación que se utiliza es JSON.

   @Path("cities")
   @Produces("application/json") 
   @Consumes("application/json")
   public class CityResource {
    ....
   }

Manejo de Excepciones

Cuando en las clases que implementan los servicios (o en los métodos de otras clases que que estos llaman) se lanza una Excepción la implementación de los servicios REST debe transformarla en un código de error HTTP para que sea enviado como respuesta al cliente que invocó el servicio.

Por ejemplo, si al intentar crear una ciudad (POST /cities) la aplicación identifica que el código proporcionado ya existe, se debe disparar una excepción. El mensaje de la excepción debe indicar claramente la causa del error. En el código, que implementa el mock de la lógica, podemos ver que si tratamos de crear una ciudad que ya existe, el método dispara una excepción

throw new CityLogicException("Ya existe una ciudad con ese id");

JAX-RS ofrece servicios para que la excepción que ocurrió en java pueda ser mapeada a una respuesta HTTP que contenga un código de error válido. Una forma fácil de lograr esto es:

Definir una clase anotada con @Provider y que implemente la interface: ExceptionMapper<E> Donde es el tipo de la excepción que se quiere procesar. La interface es la siguiente:

public interface ExceptionMapper<E extends Throwable> {
{
   Response toResponse(E exception);
}

La clase debe entonces implementar el método que devuelve la respuesta HTTP. Siguiendo con el ejemplo tenemos:

@Provider
public class CityLogicExceptionMapper implements ExceptionMapper<CityLogicException> {

    /**
     * Generador de una respuesta a partir de una excepción
     * @param ex excecpión a convertir a una respuesta REST
     */
    @Override
    public Response toResponse(CityLogicException ex) {
        // retorna una respuesta
        return Response
                .status(Response.Status.NOT_FOUND)    // estado HTTP 404
                .entity(ex.getMessage())            // mensaje adicional
                .type("text/plain")
                .build();
    }

}

results matching ""

    No results matching ""