4 octobre 2010

A bas les RPC ! RestyGWT + Jersey

A bas les RPC !

Comme le décrit bien ce post, l'un des problèmes qui se pose avec GWT c'est l'utilisation des RPC pour les échanges avec le Serveur. Cette façon de faire complique allègrement le code pour finalement pas grand chose (RPC, RPCAsync, protocole non transparent...).
Le post de Zack Grossbart propose une solution simple, utiliser les mécanismes REST pour communiquer avec les serveurs.
REST is everything GWT RPC isn’t: fast, easy to debug, and simple. It also does more.

Cela implique cependant deux choses,
  • pouvoir manipuler REST dans GWT et en Java
  • implémenter REST pour les communications côté serveur
Sachant qu'un autre critère pour tirer un bénéfice du passage à REST est que cela doit pouvoir se faire sans dupliquer a priori le modèle métier (partage des POJOs entre client et serveur), ce qui est l'une des possibilités natives des RPC GWT (pas de duplications du modèles métier, pas de DTO).

En résumé: Comment remplacer les mécanismes RPC de GWT par des appels REST sans -trop- toucher à mon existant ?

L'une des solutions possibles pour le client Web est disponible depuis peu en version 1.0 s’appelle RestyGWT, pour les aspects REST côté serveur nous utiliserons Jersey qui implémente les spécifications JAX-RS


Dans GWT

La façon de faire de RestyGWT est assez intéressante, il suffit en effet de définir un ServiceRest comme ceci pour être ensuite capable de faire des appels via REST :

public interface MyResource extends RestService {
 
    @POST
    public void save(Item item,MethodCallback<Item> callback);
    
    @GET
    public void list(MethodCallback<list<Item>> callback);
    
}

Une fois cette Resource définie, on peut l'appeler n'importe où dans le code client, en faisant par exemple :

final MyResourceservice service= GWT.create(MyResource.class);
final Resource resource = new Resource(GWT.getHostPageBaseURL()+"rest/item");
((RestServiceProxy) service).setResource(resource);
service.save(anyItem, new MethodCallback<Item>() {
    @Override
    public void onSuccess(Method method, Item response) {
        doSomethingAjaxly(response);
    }

    @Override
    public void onFailure(Method method, Throwable exception) {
        doSomethingOnError();
    }
});

Et voilà, tout est déjà fait, les appels seront transmis au serveur sur l'url http://monserveur.com/rest/item au format Json. RestyGWt propose aussi des APIs REST permettant de manipuler autre chose que du JSON pour la sérialisation, mais dans ce cas, impossible d'utiliser le RestService.


Et sur mon Tomcat alors ?

Du côté du serveur, Jersey permet d'obtenir des resources Rest simplicimes et respectant les standards. Considérons que l'on a déjà configurer Jersey pour démarrer dans le web.xml comme décrit sur le guide utilisateur de jersey, il suffit alors de coder la classe suivante pour obtenir un consommateur/producteur d'objets Json pour notre client GWT.

@Path("/item")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ItemResource{
 
    @GET
    @Path("list")
    public List<Item> list(){
        return ...list my items ...;
    }
 
    @POST
    public Item save(Item item){
        return ...save my item...
    }
}

Et si on allait plus loin ? (Et sur mon Cloud alors ?)

A condition d'utiliser un mécanisme de persistance tolérant les annotations JPA, il est possible de faire traverser toutes les couches à nos objets métiers sans effort (couche GWT <=> Servlet <=> JPA).Il est par exemple possible d'utiliser JPA sur l'appengine et ainsi déployer nos ressources REST et notre application GWT sur l'infrastructure de Google. Pour cela, en utilisant Objectify sur notre entité Item, on peut agrémenté la resource ItemResource de la manière suivante :

@Path("/item")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ItemResource extends DAOBase{
 
    static {
        ObjectifyService.register(Item.class);
    }
 
    @GET
    @Path("{id}")
    public Item get(@PathParam("id") Long id){
        try{
            //Requête sur le DataStore Google
            return ofy().get(new Key<Item>(Item.class, id));
        }catch(NotFoundException notFoundEx){
            throw new WebApplicationException(notFoundEx, Status.NOT_FOUND);
        }
    }
 
    @GET
    @Path("list")
    public List<Item> list(){
        //Requête sur le DataStore Google       
        return ofy().query(Item.class).limit(10).order("-id").list();
    }
 
    @POST
    public Item save(Item hike){
        //Requête sur le DataStore Google 
        final Key<Item> key = ofy().put(hike);
        hike.setId(key.getId());
        return hike;
    }
}

Remarque : Dans notre cas nous avons choisi de ne pas passer par une couche supplémentaire, ce qui nous force à faire étendre DAOBase par ItemResource (qui n'est plus un POJO).

Moralité 
Voilà comment en 2/3 classes (et un peu de configuration que je n'ai pas détaillée ici) il est possible de faire communiquer une application Web et un serveur Web sur la base du même modèle métier en se passant de RPC et en se basant sur des standards Java.


Libs
Jersey https://jersey.dev.java.net/
Jersey Repo : http://download.java.net/maven/2/com/sun/jersey/jersey-archive/
RestyGWT : http://restygwt.fusesource.org/documentation/index.html
Objectify : http://code.google.com/p/objectify-appengine/

Blogs
Hackito Ergo Sum : http://www.zackgrossbart.com/hackito/
GAE4J + JAX-RS : http://blog.iparissa.com/google-app-engine-jax-rs-jersey/


Aucun commentaire:

Enregistrer un commentaire