Java API for RESTful Web Services

Les services REST sont un ensemble de règle afin de construire des services.

Il a fallu attendre longtemps avant d'avoir une spécification jee pour les services REST.

Il sont plus simple que les services SOAP.

L'approche REST est radicale :

=> Ils sont liés au protocole HTTP.

Ils sont simples à mettre en place mais on fait souvent quelques entorses à la pureté du principe.

Branche git

On crée la branche jax-rs à partir de la branche master.

$ git checkout master
Switched to branch 'master'
$ git checkout -b jax-rs
Switched to a new branch 'jax-rs'

Jersey

Jersey est l'implémentation standard java pour les services REST.

Coté serveur, il suffit d'annoter les méthodes avec quelques annotations :

Il existe d'autres framework comme Resteasy et restlet.

Il est même possible (et assez simple) de faire du REST avec spring MVC.

Nous allons partir avec Jersey

<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-server</artifactId>
    <version>2.13</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <version>2.13</version>
</dependency>

Hello world

Nous allons construire un service factice qui dit bonjour.

La première classe à mettre en place est la ressource, dans le package fr.todooz.rest.

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.apache.commons.lang.StringUtils;

@Path("/hi/{username}")
public class HelloWorldResource {
   @GET
   @Produces(MediaType.TEXT_PLAIN)
   public String hi(@PathParam("username") String username) {
       return "Hello " + StringUtils.defaultIfEmpty(username, "unknown");
   }
}

Ensuite, il faut déclarer une application REST dans le même package.

import org.glassfish.jersey.server.ResourceConfig;

public class TodoozApplication extends ResourceConfig {
    public TodoozApplication() {
        // scan des ressources dans fr.todooz.rest
        packages("fr.todooz.rest");
    }
}

Cette application enregistre toutes les ressources de notre service en faisant un scan de package.

Le dernier point est la déclaration de l'application REST dans le web.xml

<!-- Jersey servlet -->
<servlet>
   <servlet-name>rest</servlet-name>
   <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
   <init-param>
      <param-name>javax.ws.rs.Application</param-name>
      <param-value>fr.todooz.rest.TodoozApplication</param-value>
   </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>rest</servlet-name>
    <url-pattern>/rest/*</url-pattern>
</servlet-mapping>

Avec le serveur tomcat démarré, le service répond sur http://localhost:8080/rest/hi/you

Le client

Nous allons mettre en place un test d'intégration qui va valider le bon fonctionnement de notre service.

Afin que jenkins n'éxécute pas ces tests avec les tests unitaires, nous pouvons :

On crée donc la classe HelloWorldResourceTest dans le package fr.todooz.integration de src/test/java.

Il n'y a pas de génération de client comme pour les services SOAP mais juste un jar.

<dependency>
   <groupId>org.glassfish.jersey.core</groupId>
   <artifactId>jersey-client</artifactId>
   <version>2.13</version>
</dependency>

Après quoi on peut écrire.

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;

import org.junit.Assert;
import org.junit.Test;

public class HelloWorldResourceTest {
    @Test
    public void hi() {
        Client client = ClientBuilder.newClient();
        WebTarget target = client.target("http://localhost:8080").path("/rest/hi/jax-rs");

        Assert.assertEquals("Hello jax-rs", target.request(MediaType.TEXT_PLAIN_TYPE).get(String.class));
    }
}

Un peu de recul

La mise en place d'un web service REST est relativement aisée.

D'une implémentation à l'autre, il peut y avoir quelques différences pour la mise en place des ressources.

Appeler un service REST est simple.

L'api des services REST est uniforme (toujours la même) : GET, POST, PUT et DELETE.

L'implémentation de REST est exigeante si l'on respecte les 3 niveaux du pattern. Mais même si on ne le fait pas, on peut se servir de bonnes pratiques liées à REST afin construire son service.

La liste des tâches

Comme pour le service SOAP, nous allons exposer nos tâches.

Afin de réutiliser notre service de tâches, nous aimerions pouvoir écrire :

@Path("/tasks")
public class TasksResource {
   @Inject
   private TaskService taskService;

   @GET
   @Produces(MediaType.APPLICATION_JSON)
   public List<Task> tasks() {
       return taskService.findAll();
   }
}

On rajoute la librairy (MOXy) pour la transformation en json.

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-moxy</artifactId>
    <version>2.13</version>
</dependency>

JSON binding support via MOXy is a default and preferred way of supporting JSON binding in your Jersey applications since Jersey 2.0. When JSON MOXy module is on the class-path, Jersey will automatically discover the module and seamlessly enable JSON binding support via MOXy in your applications.

Et comme pour les services web, il existe un pont entre jersey et spring.

Il faut utiliser l'extension spring de jersey.

<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-spring3</artifactId>
    <version>2.13</version>
</dependency>

Jersey provides an extension to support Spring DI. This enables Jersey to use Spring beans as JAX-RS components (e.g. resources and providers) and also allows Spring to inject into Jersey managed components.

Ce qui donne sur http://localhost:8080/rest/tasks

[
    {"id":42,"createdAt":1352199458514,"title":"Read Test Driven Development","text":null,"date":1352199458514,
            "tags":"java,test driven,junit","tagArray":["java","test driven","junit"]},

    {"id":41,"createdAt":1352199458511,"title":"Read Effective Java","text":null,"date":1352199458511,
            "tags":"java","tagArray":["java"]},

    {"id":40,"createdAt":1352199458496,"title":"Read Pro Git","text":null,"date":1352199458496,
            "tags":"scm,git","tagArray":["scm","git"]}
]

En se basant sur la documentation de Jersey et ce post sur les listes d'entités, ecrire un test d'intégration très simple pour ce service.