vendredi 19 avril 2013

Why Scala: La stack (2/2)







Passons maintenant au morceau épineux. Play est un framework web dont la particularité est d'être entièrement stateless, il est mu par Netty, le serveur HTTP asynchrone de RedHat. Il offre des API pour Java comme pour Scala depuis la version 2. C'est évidemment cette dernière facette qui retient mon attention.






Play ne repose pas sur la spécification Servlet (Netty non plus d'ailleurs), le seul standard auquel il semble adhérer est HTTP. Il se décompose principalement en trois couches:


Le routeur: il défini dans un fichier les URI pris en charge par l'application ainsi que le contrôleur associé et la fonction qui sera invoquée.





GET     /                           controllers.Application.index


Au début, je me suis dit que c'était plutôt nase par rapport aux contrôleurs annotés du monde Java mais en fait cela présente un avantage non négligeable: tous les accès HTTP de l'application sont centralisés dans une seule ressource au lieu d'être disséminés dans le code. Ce point a attiré mon attention car j'ai été récemment sensibilisé sur la sécurité applicative et la maîtrise des points d'entrée de l'application est un point clef.





Les contrôleurs: ils prennent la forme d'objects Scala et les fonctions sont chargées d'intégrer le modèle (à la charge du développeur) et doivent retourner des implémentations d'Action





def index = Action {    Ok(views.html.index("Your new application is ready."))  }


Les Action permettent de spécifier la nature du retour: délégation à une vue, JSon, le code HTTP de la réponse, les cookies, etc.







La vue: il est équipé d'un moteur de templating spécifique basé sur Scala: il supporte notamment le Currying et permet de manipuler les collections 






Nous sommes dans la dimension fonctionnelle, donc le typage est primordial, les routes comme les vues sont donc typées, ce ne sont pas simplement des fichiers plats évalués au runtime mais à la compilation.






Somme toute, à quelques différences identitaires près, c'est proche de ce dont nous avons l'habitude au niveau du paradigme.






En plus d'application exemples plutôt riches, Play propose de créer un squelette d'application utilisable instantanément:






$ play new the-babel-tower
_ _
_ __ | | __ _ _ _| |
| '_ \| |/ _' | || |_|
| __/|_|\____|\__ (_)my
|_| |__/

play! 2.1-RC1 (using Java 1.7.0_07 and Scala 2.10.0-RC1), http://www.playframework.org

The new application will be created in /private/tmp/the-babel-tower

What is the application name?
> the-babel-tower

Which template do you want to use for this new application?

1 - Create a simple Scala application
2 - Create a simple Java application

> 1
OK, application the-babel-tower is created.

Have fun!

$ cd the-babel-tower/

$ play run
[info] Loading project definition from /private/tmp/the-babel-tower/project
[info] Set current project to the-babel-tower (in build file:/private/tmp/the-babel-tower/)

[info] Updating {file:/private/tmp/the-babel-tower/}the-babel-tower...
[info] Done updating.
--- (Running the application from SBT, auto-reloading is enabled) ---

[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)


La page de garde comprend une directive qui génère une vue descriptive de l'application:







@(message: String)

@main("Welcome to Play 2.0") {

@play20.welcome(message)

}



Je ne sais pas pour vous, mais moi ça m'a fait penser au fameux phpinfo(). Et bien tout ça, ça roxxe car dans le développement d'applications web en java, maven permet de faire appel à une myriade de templates d'applications, mais à chaque fois que j'en ai essayé un, j'ai trouvé des dépendances et des configurations dont je n'avais pas nécessairement besoin me contraignant à un peu de ménage. Du coup, quand je démarre un projet j'ai pris l'habitude d'un simple archetype:create que je complète manuellement. Play pose l'essentiel, c'est fonctionnel et n'attend que votre code.








Back to basics. Play est très proche des couches basses. Il est aisé de positionner des headers, des cookies ou le code de retour. Il affiche se destiner au développement d'applications massivement sollicitées et hautement scalables, du coup la philosophie n'est pas d'empiler les couches d'abstractions au dessus de HTTP et de HTML à l'opposé d'autres frameworks MVC, comme si pour tirer le meilleur parti d'une infrastructure le développeur devait maîtriser toutes les couches. En tant que geek, même si c'est une vérité de La Palice, j'abonde en ce sens et j'aime avoir du contrôle sur les couches basses.


Le revers de la médaille est qu'il ne faut pas s'attendre à une galaxie de composants à la PrimeFaces, offrant des fonctionnalités riches pour un minimum d'efforts, comme s'il y avait une relation inversée entre rapidité de développement et poids sur l'environnement d'exécution… Le salut passe donc par Twitter Bootstrap (ou autre pour les HTML Heroes).


C'est d'ailleurs ce qui m'a encouragé à évincer le moteur de templating de Play: j'ai misé sur du HTML statique complété par du MVC client (Angular) et ai pris le parti d'exploiter Play qu'au travers de requêtes REST. Comme déjà argumenté dans un autre billet, si l'architecture est sans état, autant déléguer la charge de génération des vue au client, sa CPU est gratuite pour l'application!






C'est dit et redit, Play est stateless, ce qui implique qu'aucune donnée liée à la session applicative ne figure dans la mémoire du serveur, pas même l'authentification. D'ailleurs pour faire simple il n'y a aucune API intégrée de contrôle d'accès et de gestion des rôles! Les données de session sont stockées dans un cookie de type session signé: il n'est pas forgeable et permet à chacune des instances de l'application de valider le cookie présenté grâce à une clef de cryptage partagée. Cela implique que l'utilisation de la session applicative est totalement différente d'une application web stateful, exit les objets vomis dans la session sous peine de voir le poids des transferts s'envoler et les temps de réponse s'effondrer. Décidément, le profil du développeur éligible se distingue de plus en plus de celui rencontré communément pour le développement des applications d'entreprise…






Revenons sur un point: pas d'API de sécurité intégrée, enfin j'exagère il y a un object (play.api.mvc.Security) offrant deux fonctions. Security ne suffit pas et d'ailleurs le sample Zentasks guide tout de même les newbies tels que moi en proposant une trait à ajouter aux contrôleurs des ressources soumises à contrôle d'accès: elle vérifie l'authentification comme le périmètre de l'utilisateur sur l'application. Dans ses moments là, malgré les efforts investis sur le langage, il devient clair que pouvoir lire aisément du code Scala écrit par autrui va prendre encore un peu de temps, même si après coup, les concepts s'avèrent faciles à intégrer. Je vous invite à consulter le code de Zentasks (
https://github.com/playframework/Play20/blob/master/samples/scala/zentasks/app/controllers/Application.scala#L70). Le guide de référence de la version précédente de cite également un plugin tiers (Deadbolt), j'ai préféré ne pas miser sur cette extension, il m'a paru évident qu'une montée de version du framework pourrait ne pas forcément être suivie par ses extensions, d'autant que l'histoire a démontré que la compatibilité ascendante n'était pas une priorité de Play. Coïncidence: la mention vers ce plugin n'existe plus dans la documentation 2.1, d'ailleurs le chapitre sur la sécurité a disparu… JSR-250 and @RolesAllowed, I miss you!






J'ai décidé de bâtir sur une conception REST, il va donc me falloir du JSON. En Java c'est cool: JAXB, Jackson et POJO annotés, le sujet est traité. Avec Play, ce n'est pas beaucoup plus compliqué mais c'est moche: il intègre également Jackson mais l'utilisation est programmatique (basé sur des maps) au lieu d'être déclarative:






Json.obj(
"users" -> Json.arr(
Json.obj(
"name" -> "bob",
"age" -> 31,
"email" -> "bob@gmail.com"
),
Json.obj(
"name" -> "kiki",
"age" -> 25,
"email" -> JsNull
)
)
)



L'avantage est que la transformation n'est pas soudée à un modèle.







J'ai parlé de Slick précédemment et l'intégration se fait sans surprise, manuellement! Avec le cake pattern. Il y a néanmoins un projet d'intégration que je n'ai pas testé (
https://github.com/freekh/play-slick)






C'est la tendance, pour rationaliser les threads, les requêtes http sont servies de façon asynchrone. Toutefois Play brouille un peu les pistes car il permet que le contenu de l'action du contrôleur soit lui même asynchrone (liée à des I/O, charge transférée à un backend, ou délégué à un autre pool de threads):






def index = Action {
val futureInt = scala.concurrent.Future { intensiveComputation() }
Async {
futureInt.map(i => Ok("Got result: " + i))
}
}







Du coup de prime abord, on est en droit de se poser en toute bonne fois une question parfaitement fausse: pourquoi les actions ne sont elles pas asynchrones par défaut comme peut le proposer Grizzly (embarqué dans Glassfish)? Mais pas de doute, Play traite bel et bien les requêtes de façon asynchrone par défaut, vous en trouverez l'explication détaillée 
ici .






Play revendique être destiné au développement des applications web modernes, à ce titre il documente les types de restitutions un peu particuliers comme les réponses par morceaux (chunked responses), les comets sockets et les web sockets. Tout ça c'est très geek et permet de pousser des données du serveur vers le navigateur. C'est évidemment très apprécié. Ce qu'il l'est moins c'est l'API associée: les Enumerator, Enumeratee and co. Toutefois, ce n'est pas parce que je trouve la conception indigeste qu'elle est mauvaise et je pars du postulat que les gens qui s'aventurent à écrire des frameworks sont forcément brillants, mais quand je suis tombé sur un article titré
"Understanding Play2 Iteratees for Normal Humans", je me suis dit que je n'avais pas du être le seul… J'ai un peu transpiré mais j'ai fini par réussir à utiliser cette API.






Pour continuer sur l'aspect 'application moderne', Play 2 gère les sources CoffeeScript et LESS CSS. C'est cool d'autant que si le code contient une erreur de 'transpilation', elle est identifiée et mise en forme dans le navigateur (il fait mieux qu'IntelliJ sur ce point là!). 






Les modifications du code prises en compte à chaud en mode développement est un autre point positif de Play. Cela change par rapport à Java, car sans JRebel, il faut s'en remettre aux limites du hot code replace du compilateur, ou encore paramétrer le conteneur pour scanner les différences et redéployer l'application le cas échéant. Le coût de la compilation des classes Scala est largement compensé par le fonctionnement automatique, de plus le serveur ne conservant pas l'état des clients, c'est transparent côté navigateur. Un bon point.






Alors finalement, après ce florilège de points tantôt à l'avantage de Play et tantôt pas, que puis-je proposer comme conclusion? D'abord, a-t-il sa place dans la galaxie des frameworks web? Oui assurément, car j'ai décrété précédemment que Scala avait la sienne dans l'écosystème et il faut une technologie web qui repose sur ce langage. Mais du coup Play 2 Scala, c'est pour qui? Même réponse que pour Scala: pour des développeurs murs ou de jeunes stars.
Simple ou simpliste? Il y a un dogme sur lequel j'aimerais m'attarder, Play est souvent qualifié de 'stack légère' et du coup on a l'impression que tout est facile et rapide. Alors on l'a démontré, c'est rapidement prêt à coder, c'est léger à héberger mais la productivité ne me semble pas au rendez-vous, en tous cas au niveau des IHM, le budget développement le plus important d'un projet web: pas ou peu de composants ou autre taglib, il faut taper du HTML et du Javascript, c'est évidemment tendance mais sûrement pas productif. Donc légère implique également légère en fonctionnalités! Ces éléments nous apporte la réponse à la question 'dans quel contexte utiliser Play?': s'il faut des stars et que c'est long à développer, eh bien pas pour le développement web d'entreprise déjà! D'autant que j'ai vu des applications JSF 1.2 codées moyennement se comporter correctement jusqu'à 900 utilisateurs concurrents sur un noeud modeste avec une JVM 32bits, donc avant d'avoir un besoin des atouts de Play, il faut être sûr d'avoir une activité soutenue, ou avoir un budget d'hébergement faible et des capacités en développement (du temps et pas d'argent… PME ou artisanat, non?). Ce qui me pousse à penser qu'il est résolument taillé pour le web, le vrai, pas l'intranet, et même pour le Cloud (alerte #buzzword) vu son appétit d'oiseau en ressources matérielles, d'ailleurs l'éditeur ne s'y trompe pas et propose de déployer chez différents fournisseurs en quelques commandes l'application fraîchement générée.  














Voilà, c'est la fin de mon périple en Fonctionnalie. J'étais parti plein d'espoirs fondés sur ces technologies alternatives, qu'en reste-t-il aujourd'hui? J'ai pris du plaisir à coder d'une autre manière, Scala m'a montré de nouveaux horizons et obligé à revoir des algorithmes de base et ça rafraîchit lorsque le quotidien est meublé par de la "tuyauterie". La pile proposée m'a demandé de me rapprocher des couches basses et j'ai kiffé. Ca c'est pour le côté geek. Maintenant si on devait m'annoncer que mon karma était de ne jamais pouvoir programmer avec ces outils que me manquerait-il le plus? Les lambdas et la fluidité de l'API des collections, les case classes, le pattern matching et l'Option. Voilà pour le langage, mais du côté des frameworks, Slick est fun mais actuellement décevant et JPA est maîtrisé et complet (l'une des specs les plus importantes de JavaEE); Play est hype, agréable mais ne révolutionne pas et j'ai l'impression que le défunt Struts 1 était plus riche! De plus Play est stateless, mais il n'est que stateless; or si les technos web du JCP s'appuient sur des conceptions stateful, il reste néanmoins possible de développer des applications sans état avec les frameworks habituels.  


En anticipant à peine, Java8, ses lambdas et son API Stream conjugués à Lombok (pour donner aux beans la concision des case classes mais pas toutes leurs fonctionnalités)  et à L'Optional de Guava  apparaissent être des palliatifs acceptables... en tous cas en attendant de voir si la stack autour de Scala arrive à se démocratiser.



4 commentaires:

Loïc Descotte a dit…

Hello,

Pour JSON il y a plus simple, le mapping peut être fait automatiquement : http://mandubian.com/2012/11/11/JSON-inception/

Pour les modules je ne peux que recommander de les utiliser, car c'est une volonté des développeurs de garder le coeur léger et de mettre beaucoup de fonctionnalités dans les modules externes. Comme pour Rails, si on se passe des modules on passe à côté de beaucoup de choses...

Struts plus riche, je trouve ça un peu (beaucoup) exagéré, Struts ne propose pas les websockets, Json, l'accès aux bases de données, etc.

Brice Leporini a dit…

Struts plus riche, je trouve ça un peu (beaucoup) exagéré, Struts ne propose pas les websockets, Json, l'accès aux bases de données, etc

Evidemment ma remarque est volontairement démago, mais y a débat quand même... N'empêche qu'il est léger en fonctionnalités!

Loïc Descotte a dit…

Héhé oui je me suis laissé avoir dans la provoc alors :)

Sinon tu n'as pas parlé d'Anorm qui est la lib Play par défaut pour la couche persistence, quel est ton impression sur cette partie (si tu as eu l'occasion de jouer avec) ?

J'ai fait un petit comparatif ici d'Anorm et Slick http://coffeebean.loicdescotte.com/2013/03/comparaison-danorm-et-slick-pour-lacces.html

Brice Leporini a dit…

J'avais regardé en diagonale Anorm en même temps que Slick... Et j'ai décidé d'approfondir Slick, séduit notamment par le requêtage basé sur les expressions for et ai écarté Anorm car la génération du SQL est à la charge du développeur.