Accueil > Non classé > GWT : Big Fat deRPC

GWT : Big Fat deRPC

Le framework GWT met à disposition du développeur plusieurs API pour communiquer avec un serveur en HTTP.

L’une des possibilités offertes est l’utilisation du framework deRPC, présent depuis GWT 2.0. Cette nouvelle implémentation de GWT RPC est top moumoute, à tel point que Sami Jaber, dans son (très bon) livre Programmation GWT 2, considère l’ancienne API comme dépréciée et invite à utiliser uniquement deRPC.

Alors, faut-il adopter deRPC les yeux fermés ? Attention malheureux ! DeRPC peut considérablement augmenter la taille de vos requêtes HTTP.

Au cours d’une mission, j’ai ainsi découvert la multiplication par 50 de la taille d’une requête, de 200 Ko à 10 Mo.

deRPC, késako ?

Fais la diff’

Sans deRPC, les données sont sérialisées côté serveur dans un format optimisé, puis désérialisées en Javascript côté client.

Avec deRPC, on ne sérialise plus les données, mais le code JavaScript permettant de recréer la structure de données. Ainsi, le client peut désérialiser très rapidement la structure de données, en passant à la méthode eval() le contenu de la réponse du serveur.

Mékeskidit ??

Prenons un exemple simple, un contact avec un nom et un email :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Contact implements Serializable {
    private static final long serialVersionUID = 1L;

    private String fullName;
    private String email;

    Contact() {}

    public Contact(String fullName, String email) {
        this.fullName = fullName;
        this.email = email;
    }

    public String getFullName() {
        return fullName;
    }

    public String getEmail() {
        return email;
    }
}

Notre service GWT RPC retourne un new Contact("John Smith", "john@smith.com");.

Avec l’ancienne méthode de sérialisation, voici le contenu de la réponse :


1
//OK[3,2,1,["com.excilys.blog.bigrpc.client.Contact/1060582970","john@smith.com","John Smith"],0,6]

Avec deRPC :


1
function Kd$(_0,_1,_2){_0.b=_1;_0.c=_2;return _0}return [Kd$(new Kd,"john@smith.com","John Smith")];

Si je traduis :


1
2
3
4
5
6
function construct(contact, email, fullName){
    contact.email = email;
    contact.fullName = fullName;
    return contact
}
return [construct(new Contact, "john@smith.com", "John Smith")];

Pourquoi utiliser deRPC ?

Quels sont les améliorations apportées par deRPC ?

  • La désérialisation côté client est bien plus rapide sur certains navigateur (notamment IE).
  • Auparavant GWT devait générer à la compilation du code de désérialisation différent pour chaque navigateur, désormais il suffit d’un appel à la fonction eval() disponible partout.
  • Le fonctionnement du mode dev est simplifié (plus besoin de générer et d’échanger un fichier gwt.rpc).
  • Ce nouveau mécanisme est plus facile à étendre pour y intégrer ses propres fonctionnalités.

Big Fat deRPC

La documentation deRPC précise :

The payload size may be larger, which may be a consideration on slower networks or when there are many RPCs.

A mon sens, cet avertissement est bien léger face à l’explosion potentielle de la taille des requêtes.

Afin d’en rendre compte, j’ai créé un projet de démonstration, dont les sources sont disponibles sur GitHub.

Supposons que nous souhaitions sérialiser une classe Data, portant une liste de TreeMap :


1
2
3
4
5
6
7
8
9
10
public class Data implements Serializable {
    private static final long serialVersionUID = 1L;

    private List<TreeMap<Integer, Integer>> big;

    Data() {}
    public Data(List<TreeMap<Integer, Integer>> big) {
        this.big = big;
    }
}

Nous allons simuler un certain volume de données à grand coups de random :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Data generateData() {
    List<TreeMap<Integer, Integer>> big = new ArrayList<TreeMap<Integer, Integer>>();

    Random random = new Random();

    for (int i = 0; i < 1000; i++) {
        TreeMap<Integer, Integer> map = new TreeMap<Integer, Integer>();

        for (int j = 0; j < 20; j++) {
            map.put(random.nextInt(), random.nextInt());
        }
        big.add(map);
    }

    return new Data(big);
}

Les matheux auront noté que notre objet Data porte 1000 TreeMaps de 20 tuples d’entiers chacune. Si l’on prend en compte la taille d’un int et l’age du capitaine, on peut donc en déduire que ça fait beaucoup quand même.

Il ne reste plus qu’à effectuer les appels RPC, et à observer le volume des réponses (par exemple grâce à l’excellent Firebug) :

2.9 Mo ?! La requête est près de 14 fois plus large !


1
2
function Xd$(_0,_1){_0.b=_1;return _0}function Zs$(_0,_1){_0.c=_1;return _0}function jo$(_0){return hl(jo,{24:1, 31:1},0,_0)}return [Xd$(new Xd,(_._vmw=new yJ,hx(Zs$(_._vmy=new Zs,jo$([1000,(_._14=Ux(Zs$(_._vn0=new Zs,jo$([null,20,(_._0=Uw(Zs$(_._vn2=new Zs,jo$([-1798498194]))),Tw(_._vn2,_._0),_._0),(_._1=Uw(Zs$(_._vn2=new Zs,jo$([738718821]))),Tw(_._vn2,_._1),_._1),(_._2=Uw(Zs$(_._vn2=new Zs,jo$([-1713395523]))),Tw(_._vn2,_._2),_._2),(_._3=Uw(Zs$(_._vn2=new Zs,jo$([1364086016]))),Tw(_._vn2,_._3),_._3),(_._4=Uw(Zs$(_._vn2=new Zs,jo$([-1619878552]))),Tw(_._vn2,_._4),_._4),(_._5=Uw(Zs$(_._vn2=new Zs,jo$([1975253595]))),Tw(_._vn2,_._5),_._5),(_._6=Uw(Zs$(_._vn2=new Zs,jo$([-1128627208]))),Tw(_._vn2,_._6),_._6),(_._7=Uw(Zs$(_._vn2=new Zs,jo$([1188388060]))),Tw(_._vn2,_._7),_._7),(_._8=Uw(Zs$(_._vn2=new Zs,jo$([-683535658]))),Tw(_._vn2,_._8),_._8),(_._9=Uw(Zs$(_._vn2=new Zs,jo$([-1378248829]))),Tw(_._vn2,_._9),_._9),(_._a=Uw(Zs$(_._vn2=new Zs,jo$([-573610719]))),Tw(_._vn2,_._a),_._a),(_._b=Uw(Zs$(_._vn2=new Zs,jo$([-961891499]))),Tw(_._vn2,_._b),_._b),(_._c=Uw(Zs$(_._vn2=new Zs,jo$([-513024697]))),Tw(_._vn2,_._c),_._c),(_._d=Uw(Zs$(_._vn2=new Zs,jo$([-1034430367]))),Tw(_._vn2,_._d),_._d),(_._e=Uw(Zs$(_._vn2=new Zs,jo$([-411914245]))),Tw(_._vn2,_._e),_._e),(_._f=Uw(Zs$(_._vn2=new Zs,jo$([2081766648]))),Tw(_._vn2,_._f),_._f),(_._g=Uw(Zs$(_._vn2=new Zs,jo$([15252921]))),Tw(_._vn2,_._g),_._g),(_._h=Uw(Zs$(_._vn2=new Zs,jo$([1826494923]))),Tw(_._vn2,_._h),_._h),(_._i=Uw(Zs$(_._vn2=new Zs,jo$([33421800]))),Tw(_._vn2,_._i),_._i),(_._j=Uw(Zs$(_._vn2=new Zs,jo$([-502089454]))),Tw(_._vn2,_._j),_._j),(_._k=Uw(Zs$(_._vn2=new Zs,jo$([502862973]))),Tw(_._vn2,_._k),_._k),(_._l=Uw(Zs$(_._vn2=new Zs,jo$([155637684]))),Tw(_._vn2,_._l),_._l),(_._m=Uw(Zs$(_._vn2=new Zs,jo$([635170857]))),Tw(_._vn2,_._m),_._m),(_._n=Uw(Zs$(_._vn2=new Zs,jo$([-328120823]))),Tw(_._vn2,_._n),_._n),(_._o=Uw(Zs$(_._vn2=new Zs,jo$([706234009]))),Tw(_._vn2,_._o),_._o),(_._p=Uw(Zs$(_._vn2=new Zs,jo$([508490557]))),Tw(_._vn2,_._p),_._p),(_._q=Uw(Zs$(_._vn2=new Zs,jo$([786903066]))),Tw(_._vn2,_._q),_._q),(_._r=Uw(Zs$(_._vn2=new Zs,jo$([-1796842138]))),Tw(_._vn2,_._r),_._r),(_._s=Uw(Zs$(_._vn2=new Zs,jo$([920929371]))),Tw(_._vn2,_._s),_._s),(_._t=Uw(Zs$(_._vn2=new Zs,jo$([169582333]))),Tw(_._vn2,_._t),_._t),(_._u=Uw(Zs$(_._vn2=new Zs,jo$([930509506]))),Tw(_._vn2,_._u),_._u),(_._v=Uw(Zs$(_._vn2=new Zs,jo$([1832772225]))),Tw(_._vn2,_._v),_._v),(_._w=Uw(Zs$(_._vn2=new Zs,jo$([999207309]))),Tw(_._vn2,_._w),_._w),(_._x=Uw(Zs$(_._vn2=new Zs,jo$([-964770637]))),Tw(_._vn2,_._x),_._x),(_._y=Uw(Zs$(_._vn2=new Zs,jo$([1093127442]))),Tw(_._vn2,_._y),_._y),(_._z=Uw(Zs$(_._vn2=new Zs,jo$([1968504258
// Je n'ai mis qu'une infime partie, ça continue comme ça pendant... très longtemps ;-)

Pour reproduire ces tests, il vous suffit de télécharger le projet Eclipse.

Conclusion : deRPC, caca ou pas caca ?

Faut-il abandonner deRPC ? Certainement pas, car cette solution surclasse l’ancien mécanisme de RPC à bien des égards.

Le problème est plus lié aux structures de données utilisées – ici, une liste de treemap – qu’au volume. La plupart du temps, vous pouvez utiliser deRPC sans crainte. Pensez simplement à garder un œil sur les tailles des requêtes combinant structure de données complexe et fort volume.

Notez par ailleurs qu’il est tout à fait possible de disposer de services deRPC et GWT RPC au sein de la même application, et que les changements à réaliser pour passer de l’un à l’autre sont minimes.

En guise de conclusion : d’après David Chandler (un googler), deRPC sera probablement déprécié, au profit de RequestFactory. Tout ça… pour ça ?

Share
  1. 19/05/2011 à 13:47 | #1

    Bon article.
    Je l’ai également observé sur des structures de données assez élaborées.

    A noter également qu’on peut également optimiser (en espace) :
    //OK[3,2,1,[“com.excilys.blog.bigrpc.client.Contact/1060582970″,”john@smith.com”,”John Smith”],0,6]
    en masquant le nom des classes.

  1. Pas encore de trackbacks