sábado, 10 de julio de 2021

RESTEasy: Como crear un Web Service y su cliente

Ya hemos visto varios frameworks que nos pueden ayudar a crear un servicio web y su cliente. Hoy veremos RESTEasy que es un framework de JBoss, el cual ofrece una implementación para las especificaciones JAX-RS 2.0 y 2.1. Y como es normal, aunque esta pensado para una integración perfecta con WildFly, también nos ofrecerá la posibilidad de desplegarlo en cualquier otro contenedor de servlets como Tomcat o Jetty. 

Empecemos con el Web Service, el cual será muy básico, lo necesario como para mostrar como poder montar uno más complejo. Lo primero será indicar cuales son las dependencias mínimas necesarias:

  • resteasy-servlet-initializer
Nos permite generar el Web Service sin necesidad de configuración manual por nuestra parte. Es decir, con las anotaciones que veamos a continuación será suficiente para configurar nuestro servicio. No necesitaremos de configurar nada en el fichero web.xml tal y como hicimos en este otro post. Incluso podriamos no crear un fichero web.xml, pero esto no lo haremos para evitar problemas con Maven. 

Esta a su vez tiene como dependencia a las librerías principales del framework: resteasy-core y las librerías con las implementaciones de la especificaciones.  

  • resteasy-jackson2-provider
Es la librería que nos permite la transformación de la respuesta en formato de objeto Java a JSON. En el caso de que quisiera responder XML debería incluir la librería resteasy-jaxb-provider. Si no incluimos la librería podemos tener un error del tipo: Could not find MessageBodyWriter for response object of type: com.home.example.resteasy.bean.Book of media type: application/json.

Una vez que hemos indicado las dependencias en el fichero pom.xml y que sabemos que nos vale con un fichero web.xml bien foramdo pero sin contenido. Vamos a configurar el servicio en dos sencillos pasos. 

Debemos crear una clase que extienda de javax.ws.rs.core.Application. Y que además tenga la anotación javax.ws.rs.ApplicationPath con la cual podremos indicar el path principal de nuestro Web Service. Por tanto, para poder acceder a los métodos REST deberemos por un lado indicar el nombre del servicio y por otro el application path indicado en la anotación. Ejemplo:

@ApplicationPath("/library")
public class LibraryApplication extends Application {
}
 
El siguiente paso será crear los métodos REST. Lo podremos hacer a través de distintas anotaciones:
  • @Path: Nos permite indicar un path común para todos los métodos. 
  • @POST, @GET, @DELETE o @PUT: Indica a que método HTTP esta asociado el método. 
  • @Produces, @Consume: Permite indicar que formato tendrá la entrada o salida. JSON o XML. 
  • @PathParam, @QueryParam: Permite indicar como recibiremos parámetros a través de la URL de invocación. 
Además de esta hay otras anotaciones que nos permitirán una mayor configuración de los métodos, indicar las cookies, cabeceras, valores por defecto o incluso si los parámetros vienen codificados. 

Si queremos devolver un objeto en un formato concreto no necesitaremos nada más que devolver dicho objeto. Pero si queremos devolver una respuesta más personalizada, donde indiquemos cookies, código de respuesta, etc, lo haremos a través del objeto javax.ws.rs.core.Response. Ejemplo:

@Path("/book")
public class BookServiceImple {
    private static Map<Integer, Book> library = new HashMap<Integer, Book>();
    static {
	library.put(1, new Book(1, "Ender's Game", "Orson S. Card"));
	library.put(2, new Book(2, "The stars my destination", "Alfred Bester"));
    }
    @POST
    @Path("/")
    @Produces({ MediaType.APPLICATION_JSON })
    @Consumes({ MediaType.APPLICATION_JSON })
    public Response create(final Book book) {
	if (null != library.get(book.getId())) {
	    return Response.status(Response.Status.NOT_MODIFIED).entity("Book is already in the library.").build();
	}
	library.put(book.getId(), book);
	return Response.status(Response.Status.CREATED).build();
    }
    @GET
    @Path("/{id}")
    @Produces({ MediaType.APPLICATION_JSON })
    public Book read(@PathParam("id") final Integer id) {
	if (library.containsKey(id)) {
	    return library.get(id);
	} else {
	    return null;
	}
    }
}

Si queremos utilizarlo, deberemos invocar el comando Maven: clean package y desplegar el fichero .war generado en nuestro servidor de contenedores o aplicaciones favorito. Y ya podremos utilizarlo invocando el siguiente comando:

curl --location --request GET 'http://localhost:8080/RestEasyService/library/book/1'

Nuestro siguiente paso será crear el cliente. El cual podremos crearlo de dos formas diferentes, pero siempre haciendo uso de la clase de utilidad ResteasyClientBuilder. La cual cuenta con un método básico de creación del cliente pero que también aporta métodos para la configuración de dicho cliente, además de permitirnos crear un objeto de tipo WebTarget. 

Una vez en este punto, podemos realizar la invocación del Web Service de dos formas diferentes. La primera de ellas, será algo más rudimentaria y necesitará de una mayor configuración para cada una de las llamadas, así como indicar la URL exacta a la hora de crear el objeto WebTarget. Este objeto nos permitirá hacer las llamadas REST correspondiente a través de sus métodos get() o post() e indicando que tipo de objeto vamos a recibir. 

public class BookClient {
    final static String path = "http://localhost:8080/RestEasyService/library/";
    public Book read_target(final Integer id) {
	Book retorno = null;
	WebTarget target = ClientBuilder.newClient().target(path + "book/" + id);
	try (Response response = target.request().get()) {
	    retorno = response.readEntity(Book.class);
	}
	return retorno;
    }
}

Para la segunda manera de crear el cliente utilizaremos el framework de RESTEasy Proxy. El cual nos permitirá realizar invocaciones más sencillas pero como punto negativo tendremos que crear una Interface que cumpla con el contrato del Web Service. El mayor inconveniente de esta interface es que tendremos que crearla manualmente a través de anotaciones JAX-RS, pero lo más positivo es podremos crear clientes de Web Services que no tienen porque haber sido generados a su vez por JAX-RS. Un ejemplo:

@Path("/book")
public interface BookService {
    @POST
    @Path("/")
    @Produces({ MediaType.APPLICATION_JSON })
    @Consumes({ MediaType.APPLICATION_JSON })
    Response create(final Book book);

    @GET
    @Path("/{id}")
    @Produces({ MediaType.APPLICATION_JSON })
    Book read(@PathParam("id") final Integer id);
}

Para crear un objeto de tipo proxy, lo podremos obtener a través del objeto WebTarget, indicando la interfaz que tiene el contrato del Web Service. Para este caso no necesitaremos indicar la URL exacta de cada uno de los métodos y las invocaciones y tratamientos de la respuesta, la realizaremos de una forma más natural y comprensible. 

public class BookClient {
    final static String path = "http://localhost:8080/RestEasyService/library/";
    private static BookService proxy;
    static {
	WebTarget target = ClientBuilder.newClient().target(path);
	proxy = ((ResteasyWebTarget) target).proxy(BookService.class);
    }
    public Book read_proxy(final Integer id) {
	return proxy.read(id);
    }
}

Con esto ya sabemos crear un Web Service y su cliente pero vamos a ver un último detalle importante como es la configuración a través de la clase MicroProfile Config.

Esta clase nos permitirá cumplir con la metodología de 12factor, permitiéndonos configurar el cliente o servicio a través de variables de entorno, propiedades del sistema o un fichero de configuración específico. A continuación veremos como podemos modificar la creación del cliente anterior para que obtenga la dirección del servicio a través de una variable de entorno. 

static {
    Config config = ConfigProvider.getConfig();
    String path = config.getValue("BOOKWS_URL", String.class);
    WebTarget target = ClientBuilder.newClient().target(path);
    proxy = ((ResteasyWebTarget) target).proxy(BookService.class);
}

Por prioridad primero intentará obtener el valor de una propiedad del sistema, después de una variable del entorno y por último lo buscará en el fichero del classpath META-INF/microprofile-config.properties. Aunque estas valores de prioridad y orígenes de información también son configurables. Esto nos permitirá mantener el código de la aplicación inmutable en cada entorno en el cual despleguemos nuestra aplicación.

Y hasta aquí las nociones básicas para crear Web Services y clientes con el framework de JBoss. Si quieres puedes ver el código fuente aquí

No hay comentarios:

Publicar un comentario