mercredi 6 octobre 2010

Exposer un service REST/JSON sur le Cloud avec Google APP Engine

Petite introduction au Cloud et à Google APP Engine
Google propose une plateforme Cloud de type PaaS (plateforme as a service) : App Engine.
Le principe du PaaS est de fournir une plateforme applicative hébergée en ligne et prête à l'emploi.
C'est un des trois niveaux de services offerts par les infrastructures de type Cloud, les deux autres niveaux étant IaaS (infrastructure as a service, on met à disposition des ressources physiques, CPU et espace disque), et SaaS (on met à disposition une application en ligne, comme Google Documents par exemple).

La plateforme App Engine supporte (un sous ensemble de) la plateforme Java. Elle propose entre autres un conteneur de Servlet et une base de données qui nous permettent d'héberger des application Java sur les serveurs de Google.
Elle offre une utilisation gratuite en dessous de certains quotas d'utilisation.
C'est donc une plateforme idéale pour se lancer dans des tests pour des prototypes : on ne paie que si on a déployé un produit qui commence à rencontrer du succès.

Google promet une scalabilité infinie et une montée en charge lisse et transparente. C'est la fameuse élasticité du Cloud. Si votre application ne demande que peu de ressources, le CPU et le disque qui lui seront alloués seront faibles. Au fur et à mesure que les besoin augmenteront la plateforme qui héberge votre application allouera de plus en plus de puissance pour répondre à la demande, sans que vous n'ayez rien à faire.
Si vous avez besoin de résister à une forte charge pour un évènement ponctuel (une soirée d'élections par exemple si vous avez une application dédiée à l'actualité politique), vous n'aurez pas besoin de payer de gros serveurs toute l'année, vous paierez pour votre consommation le jour J et le reste de l'année vous retournerez sur une facturation plus modeste, voire nulle.

C'est vraiment de la consommation de ressources à la demande, et  c'est là toute la magie et la force d'une plateforme de PaaS.

Mise en oeuvre
Dans cet article nous allons voir comment écrire un petit service REST qui renvoie du JSON. Ce service étant exposé sur le Cloud, il sera accessible de n'importe où. Etant de type REST, il pourra être utilisé par n'importe quel outil ou technologie capable d'effectuer une requête HTTP : un navigateur, une application mobile (un client android ou iphone), une application Javascript (une extension Google Chrome ou Firefox, une application Adobe Air), etc.



Installation de l'outillage
Nous allons utiliser le plugin Eclipse fourni par Google pour créer et déployer notre petite application backend sur le Cloud.

Ce plugin est installable à partir du gestionnaire d'extensions d'Eclipse, en ajoutant l'URL suivante : http://dl.google.com/eclipse/plugin/3.6

NB : Changez la fin de l'URL si votre version d'Eclipse n'est pas la 3.6 (Helios).

Créons une nouvelle WebApp Google en utilisant ce menu :



Ecriture du service
Jersey fonctionne parfaitement sur Google App Engine. Il permet de créer facilement des services REST renvoyant des donnés au format XML ou JSON. Dans cet exemple nous allons utiliser JSON qui est directement utilisable par beaucoup de technologies côté présentation.

Ajoutez la librairie Jersey au classpath de votre projet. Elle est téléchargeable ici.

Nous allons créer un service très simple qui renvoie une liste de restaurants pour une ville donnée :

@Path("/restos")  
public class RestosWS {
 
 private DAO dao;
 
 @SuppressWarnings("unused")
 @PostConstruct
 private void init(){
  this.dao = new DAO();
 }
 
 @GET  
 @Produces("application/json")  
 public List<Resto> listRestos() {  
  List<Resto> restos = dao.listerRestos();
  Collections.sort(restos); 
  return restos;
 }  
 
 @Path("{ville}")  
 @GET  
 @Produces("application/json")  
 public List<Resto> listRestos(@PathParam("ville")String ville){  
  List<Resto> restos = dao.listerRestosParVille(ville);
  return restos;
 }  
}
}

L'annotation @Path permet de spécifier l'URI à laquelle notre service sera exposé.
Dans notre cas on pourra y accéder par http://monserver/monappli/restos ou encore
http://monserver/monappli/restos/paris pour les restaurants de la ville de Paris.

L'annotation @GET indique que la méthode sera accessible par un GET HTTP, @Produces indique le format de données renvoyé.
Pour en savoir plus pour Jersey et sur l'écriture de services REST vous pouvez consulter ces deux posts sur ce blog.

La couche de persistance
App Engine repose sur une base de données appelée BigTable. C'est une base de type NoSQL, c'est à dire non relationnelle. Il est possible d'utiliser JPA avec App Engine, mais il existe un certain nombre de limitations. En effet JPA est surtout pensé pour fonctionner avec des bases SQL, alors que BigTable fonctionne plutot comme une grosse HashMap qui stocke des clés et des valeurs.
Il existe un outil de mapping objet --> base de données pour le cloud de Google, assez light et plus pratique à utiliser : Objectify.

Voyons comment fonctionne cette petite classe DAO :

public class DAO extends DAOBase {
 
static{
 ObjectifyService.register(Ville.class);
        ObjectifyService.register(Resto.class);      
 } 
 
 public List<Resto> listerRestos(){
  return ofy().query(Resto.class).list();
 }
 
 public List<Resto> listerRestosParVille(String ville){
  return ofy().query(Resto.class).filter("ville.nom =", ville).list();
 }

public void insererDonnesBidons(){
//Initialisation de données bidons
        Ville ville = new Ville("Grenoble", 38);
 ofy().put(ville);
        ofy().put(new Resto("MonResto", ville));
 ville = new Ville("Paris", 75);
 ofy().put(ville);
 ofy().put(new Resto("MonResto2", ville));
}
}

ObjectifyService.register() permet d'enregistrer une classe auprès d'Objectify pour pouvoir ensuite persister des objets. La classe DAOBase fournie par le framework permet de bénéficier de la méthode ofy() qui renvoie une instance de classe Objectify, afin d'insérer et de lire des données. Comme vous pouvez le voir les filtres fonctionnent très simplement. On peut remplacer = par < ou > si on manipule des données numériques.

Pour que nos classes Resto et Ville fonctionnement avec Objectify, on fourni simplement un identifiant qu'on annote avec @Id (annotation standard provenant de JPA) :

public class Resto{
 
 @Id
 public Long id;
 
 public String nom;
 
 @Embedded
 public Ville ville;

} 

Le plus simple avec Objectify pour exprimer le fait qu'un restaurant inclue des information sur sa ville est d'écrire une relation de type Embeded. Notre restaurant embarque donc toutes les informations de la ville dans laquelle il se trouve.

Il serait possible de définir une notion de clé pour définir une relation "many to one" mais cela compliquerait la sérialisation JSON. En effet la notion de jointure n'est pas naturelle puisque nous ne sommes pas sur un modèle relationnel. Dans notre cas, les données seront donc dupliquées si plusieurs restaurants sont enregistrés dans la même ville. Mais c'est (entre autres) en évitant les jointures que l'on gagne en performances dans le modèle NoSQL, prévu pour répondre à de fortes charges sur de grandes volumétries.

NB : lors de l'insertion d'objets dans la classe DAO, nous n'avons pas fourni d'id. Si l'id utilisé est de type Long, Objectify gère tout seul son auto incrémentation.

Déploiement de l'application sur le Cloud
Pour déployer notre service, nous avons besoin de créer un compte sur Google App Engine.
Ensuite, en cliquant sur la petite icone en forme d'avion, on obtient cet écran qui nous permet d'envoyer notre code vers les serveurs de Google :



Premier appel 
En entrant l'URL http://monappli.appspot.com/restos dans un navigateur, on obtient le résultat suivant :
{
[{  
"id":"1001","nom":"MonResto","ville":  
{  
"nom":"Grenoble","departement":"38"  
}  
},  
{  
"id":"3001","nom":"MonResto2","ville":  
{  
"nom":"Paris","departement":"75"  
}  }  ]  
}

Dans un prochain post nous verrons comment consommer ce service avec une application mobile pour afficher à l'écran des données sur nos restaurants préférés.