La conférence ScalaDays 2011 s’est déroulée la semaine dernière à Stanford, au sud de San Francisco. Guillaume Bort et Sadek Grobi étaient là-bas pour présenter la dernière version de l’approche Scala proposée par Play! Framework. Play! Scala permet d’utiliser Scala tout en conservant l’approche simple et puissante de Play! Framework. Je ne présente plus Play! Framework, car avec 260 000 téléchargements à ce jour on ne parle plus d’un épiphénomène, mais bien d’un outil de plus en plus populaire.
Après avoir installé Play! Framework v1.2.2RC1, l’installation du module s’effectue en quelques instants avec la commande « play install », qui permet de télécharger et de configurer les modules complémentaires de Play!
$ play install scala-0.9.1 $ play new myScalaWebApp --with scala $ play run myScalaWebApp
Un moteur de template plus simple
Première différence avec la version Java, le moteur de template. Tout en conservant le nom des tags de la version actuelle, la nouvelle version Scala propose un moteur de template ultra-simple. L’idée est empruntée à Razor, le nouveau système de templating que l’on retrouve dans la toute dernière version du framework web de Microsoft. L’idée ? Au lieu d’encadrer avec le principe d’une balise ouvrante/fermante le code dynamique, il suffit de placer directement les tags, un peu finalement comme des Macros :
La version avec le moteur Groovy :
<UL> #{list products, as:'p'} <LI>${p.name} (${p.price})</LI> #{/list} </UL>
La nouvelle version avec le nouveau moteur Scala :
<ul> @for(p <- products) { <li>@p.name ($@p.price)</li> } </ul>
Ce nouveau moteur de template permet d’écrire le code de la partie vue, de manière plus fluide et plus simple, comme une suite de macro appliquée dans un template.
Chaque template dans cette nouvelle version est une fonction. Il devient donc nécessaire de préciser les paramètres en entrée de chaque template, ce qui diffère de l’approche actuelle Java+Groovy. En quelques sortes, vos templates de page dans cette version deviennent des templates typés fortement, les paramètres attendus doivent être explicitement déclarés.
Il devient aussi facile de faire du pattern matching. Remarquez aussi le mélange des balises HTML, traitées comme du XML par Scala, et la partie purement dynamique :
@connected match { case Admin(name) => { <span class="admin">Connected as admin (@name)</span> } case User(name) => { <span>Connected as @name</span> } }
Les Tags dans la version Groovy/Java sont des macros qui permettent de réutiliser à différents endroits, des bouts de code. Dans la version Scala, une nouveauté apparait implicitement : il devient possible de passer des paramètres à un Tag, mais il devient aussi possible de construire ce type d’appel :
Définissons d’abord un tag notice « views/tags/notice.scala.html »
Ce template prend 2 paramètres : un paramètre level de type String, dont la valeur par défaut sera error, ainsi qu’un bloc html :
@(level:String = "error")(body: (String) => Html) @level match { case "success" => { <p class="success"> @body("green") </p> } case "warning" => { <p class="warning"> @body("orange") </p> } case "error" => { <p class="error"> @body("red") </p> } }
Si level vaut « error », nous affichons un bloc span avec pour style css « error », puis nous rappelons le template body en passant en paramètre la valeur « red ».
Voici comment l’utiliser dans une page :
@import views.tags.html._
@notice("error") { color =>
Oops, something is wrong
}
Dans notre page principale, @notice est notre template définit comme un tag. Nous passons « error ». Cette fonction s’exécute et appelle ensuite body, en passant un paramètre de type String. Dans la page principale, je déclare une fonction qui prend un paramètre color, et qui retourne alors notre message d’erreur, avec la couleur red…
Cet exemple illustre simplement un nouveau principe disponible uniquement grâce à cette approche, où comme dirait quelqu’un l’an dernier, « tout est objet et fonction en même temps« .
Cette approche a aussi une conséquence sur la façon d’écrire et d’architecturer son application. Je pense qu’elle demandera vraiment une remise en question de notre habitude très « MVC », sagement appliquée ces 10 dernières années. Ne partez pas, on en reparle à la fin.
Anorm, un « Another NOT ORM » framework
Parlons un petit peu de JPA et de son utiliastion avec Scala auparavant. Techniquement, en utilisant l’implémentation JPA d’Hibernate, cela fonctionne. Je crois que Scala 2.8 ne supporte pas les annotations imbriquées comme @JoinTable. Il serait par exemple impossible d’écrire ce code en Scala :
// note : c'est du Java mon ami @JoinTable( name="CUST_PHONE", joinColumns= @JoinColumn(name="CUST_ID", referencedColumnName="ID"), inverseJoinColumns= @JoinColumn(name="PHONE_ID", referencedColumnName="ID") )
Deuxième question sur le concept de l’ORM et son utilisation avec Scala. Si je cherche à privilégier une approche immutable de mon domaine de données, est-ce que l’ORM reste intéressant ? Si je souhaite avoir un domaine objet pour travailler, et utiliser la base de données relationnelles comme mode de persistence, l’approche JPA+Java est parfaite. Si par contre je décide que mon domaine est avant tout en base, que le coeur de mon application travaille sur une base de données (relationnelle ou non) alors l’ORM perd de son intérêt.
Pour bien comprendre ce que j’essaye de dire, essayez d’imaginer où doit se situer la « golden source » de votre donnée. Est-ce que c’est en mémoire, dans votre serveur d’application ? Ou est-ce que votre donnée de référence doit être dans votre base de données ? Cela semble trivial, mais lorsque l’on prend l’approche sans état conversationnel, il devient plus simple de ne PAS utiliser d’ORM.
La question devient alors : quel est le meilleur DSL pour accéder à une base de données ? Les développeurs de Play! Framework se sont creusés la tête, et Sadek Grobi a proposé une approche très simple et très puissante basée sur SQL.
Anorm (Anorm is Not a Object Relational Mapper) est un nouveau moteur très puissant qui tire partie de la richesse de Scala. Je retrouve un des points forts de Play! Framework, que l’on pourrait résumer par l’approche « tu es un p$$tain de développeur oui ou merde ? » . Or comme chaque développeur, tu as fais du SQL. Cela tombe bien, Anorm ne propose pas un nouveau DSL, il se base sur SQL. Oui tu m’as bien entendu, tu vas faire du bon vieux SQL qui marche depuis la nuit des temps. Et c’est pareil pour la couche web : tu ne vas pas décrire ta page web en Java, tu vas la coder en HTML. Tu sais ce que c’est le HTML non ?
Bref le retour à la source.
Cela s’accorde bien lorsqu’en face vous cherchez (et vous trouvez) des problèmes inhérents au mélange du monde relationnel et du monde objet. Prenez par exemple une relation de type @OneToMany de type lazy. Disons une liste de Pays et de Villes, un Pays pouvant contenir plusieurs Villes. Avec une relation de type @OneToMany, si vous souhaitez construire votre ensemble Pays/Villes, vous vous retrouvez par défaut avec le problème du N+1 SELECT. Il faut alors passer par un left join dans vos @NamedQuery. Vas-y que je te rappelle que tu travailles sur un modèle relationnel, dans ton beau modèle tout objet… (voir aussi Hibernate Performance Fetching).
Ici il n’est plus question d’abstraction, puisque vous travaillez directement sur votre base de données. Un point faible de cette approche semble être la dépendance du code par rapport au schéma. Si je décide que ma base de données est la source de référence, il faudra donc que je corrige mes requêtes si le schéma change. Mais dans l’approche JPA, lorsque mon domaine change, je dois aussi reprendre le code de mon service. Bref il y a toujours quelque chose à reprendre si votre Domaine (avec un grand D) évolue. Que ce soit dans le monde objet, ou dans le monde relationnel. Comprenez une chose : soit vous ignorez la base de données, soit vous en faite une amie.
Voici un exemple tiré de la documentation de l’API, qui montre comment récupérer une liste de pays, puis ensuite construire une nouvelle collection pour la vue :
// Create an SQL query val selectCountries = SQL("Select * from Country") // Transform the resulting Stream[Row] as a List[(String,String)] val countries = selectCountries().map(row => row[String]("code") -> row[String]("name") ).toList
Un autre exemple tiré de l’exemple Yabe :
val postsWithAuthor:List[(Post~User)] = SQL( """ select * from Post p join User u on p.author_id = u.id order by p.postedAt desc """ ).as( Post ~< User * )
Vous pouvez aussi l'écrire de cette façon dans la class Post :
def allWithAuthor:List[(Post,User)] = SQL( """ select * from Post p join User u on p.author_id = u.id order by p.postedAt desc """ ).as( Post ~< User ^^ flatten * )
Puis vous pourrez vous en servir dans une action dans votre controller de cette façon :
import models._ object Application extends Controller { def index = { val allPosts = Post.allWithAuthor html.index( front = allPosts.headOption, older = allPosts.drop(1) ) } }
headOption récupère le post le plus récent pour l’afficher complètement, older est l’ensemble des Posts moins le post le plus récent que nous venons d’afficher. Pratique non ?
Nul doute qu’il sera nécessaire de bien comprendre et de challenger cette approche. Mais elle vient remettre en question le mouvement « tout-orm » du monde Java, avec une petite brise fraiche qui plaît bien.
Du code plus lisible et bien typé
L’approche Scala pour Play! est intéressante car elle rend le code plus simple et plus facile à lire. Finalement, dans l’esprit nous pouvons voir une requête Web comme l’enchainement de différentes fonctions de transformation. Soit un état de départ donné n, si j’applique ma fonction f(n) sur mon ensemble, je dois retrouver toujours le même résultat. C’est exactement la définition même d’une URI : pour un espace d’adressage donné, je retourne un ensemble de méta-données. Scala avec son approche fonctionnelle est particulièrement intéressant pour construire une application web.
C’est vraiment un pas en avant vers une architecture où le client tient les informations d’état (appelez cela la session si vous voulez) et où le serveur est complètement sans état conversationnel. Il peut cependant très bien avoir un cache de données, mais il n’y a plus la notion de session utilisateur du côté serveur. Il y a la notion de session serveur du côté client. Que ce soit dès aujourd’hui avec un Cookie, ou demain avec HTML5, c’est une approche assez différente. Je n’ai pas dit mieux ou moins bien, j’ai bien dit : une approche différente.
Prévu dans la version 1.0
La version 0.9.1 est sortie juste à temps pour Scala Days. La version 1.0 apportera d’autres concepts dans la roadmap :
Before completing the 1.0 version of Play Scala we need to work on the following aspects: - Rework the MVC layer to make it easier to compose Actions. - Add proper JSON support — Probably using Lift JSON - Provide an API to work with the Play async features. - Integrate Akka actors - Provide a type safe way to express routes directly in Scala So probably move to Scala 2.9.
Conclusion
Intéressant et très poussé, Play! Scala offre un moteur Web plus simple à utiliser que Lift pour la plateforme Scala. A titre personnel, je vais travailler dessus pendant 2 mois. Je vous ferai part de ce qui m’a plu et ce que j’ai trouvé délicat. Allez y jeter un oeil par curiosité, c’est facile à tester, le tutorial Yabe permet de se lancer rapidement, cela ne vous demandera que quelques heures.
A suivre donc.
Références :
Le site de Play! Framework : http://scala.playframework.org/
Le souci des requêtes N+1 sur StackOverflow
Guide des stratégies de chargement d’Hibernate
Bonjour Nicolas,
J’entame également depuis ce matin la refonte d’un site de réservation en JSF/Richfaces vers une application play! & Scala.
Quel IDE va tu utiliser ? J’ai commencer à essayer intellij 10.5 sans obtenir de coloration syntaxique, ni complétion.
Pour l’instant je m’oriente vers l’éditeur textmate + un bundle scala que l’on peut trouver sur github.
Tu prévois de recoder l’express board en play! + scala ? Pour des raisons de perfs ?
Hello,
merci pour cet article,
je me suis justement amusé avec play et scala tout le week end,
anorm est puissant c’est clair, il est bien dans la philosophie de « retour aux sources » et « maîtrise ton code et tes technos (ici SQL) » poussée par Play.
Mais il peut avoir un côté frustrant aussi. Quand on regarde l’exemple Yabe fournit avec play, pour faire un find by id en Java, on écrit « findById(id) ». dans la version Scala avec anorm, on écrit 4 lignes de SQL et une ligne de scala supplémentaire pour mapper les résultats dans des objets. Et c’est pareil pour toutes les requêtes, comme on perd le système de relations, on fait toutes les jointures à la main.
Mais bon je pense que c’est une question de re-habitude, à force de faire du JPA et e l’hibernante on a perdu l’habitude d’écrire du SQL. C’est comme le retour au javascript quand on vient de JSF, il faut juste un temps de réadaptation je pense.
Sinon anorm c’est simple pour des cas de base mais les exemples de « parsing combinators » de la doc montrent que dans des cas plus avancés ça pourrait dangereusement se compliquer et demander des notions de Scala plutôt avancées pour traiter les résultats de requêtes.
Donc pour résumer je trouve anorm élégant et puissant, mais j’ai un peu peur qu’il effraie ceux qui ne sont pas encore des gourous de Scala…
J’ai posé la question à Guillaume Bort sur le Google Group du projet et le support de JPA ne devrait pas être maintenu dans les futurs versions. Je comprend les arguments (hibernate pas très compatible avec Scala, il génère plein de choses qu’on ne maitrise pas, il etc.) mais ça m’a un peu déçu sur le coup…
Pour la partie web c’est pas mal du tout et c’est plutôt agréable d’utiliser Scala dans les contrôleurs!
Loic
@cestpasdur Perso j’utilise intellij avec le plugin scala, la coloration et la complétion fonctionnement bien.
Sinon textmate est nickel aussi , mais pas de complétion, ça peut gêner un peu pour apprendre le langage et découvrir les API.
@loic : Tu as fait autre chose que play idealize pour que cela fonctionne ?
Je trouve dommage que le framework semble se couper de plus en plus de java.
Dommage également de se séparer de JPA. L’équipe dans laquelle je travaillais auparavant avait réussi à « vendre » l’utilisation du framework pour la réalisation de petit projet.
S’ils continuent à s’éloigner de java pour aller vers scala sans fournir une vision claire sur leur roadmap, j’ai peur qu’ils se coupent d’une grosse partie leurs utilisateurs.
@cestpasdur Je parlais biende la version Scala pour le non support de JPA. La version Java va continuer à être basée sur JPA. Il ne devrait pas y avoir de scission avec le monde Java, juste 2 version bien distinctes du framework.
Sinon oui j’ai juste fait un play idealize. Peut-être qu’il faut que tu mettes à jour le plugin Scala d’Intellij?
@loic merci pour l’information concernant JPA.
@loic tu as débuté scala avec son utilisation dans play! En combien de temps as tu été productif/opérationnel ?
Oui j’ai débuté Scala avec Play, après quelques lectures sur le langage et quelques hello world.
Je ne peux pas vraiment dire quand j’ai été opérationnel car je fais ça à titre personnel pour le plaisir. Mais j’ai réussi à faire une version de mon appli (une simple appli de démo que j’ai écrit pour avoir des supports pour faire des tuto sur play) qui fonctionne en quelques soirées, en abusant du google group. Là je suis reparti sur le dev pour adapter mon code à play-scla 0.9.1 et j’ai encore besoin du google group donc je ne suis pas encore vraiment productif :p
Le passage de Java à Scala, surtout quand on apprend sur le tas sans formaiton, est quand même un gros pas à franchir! Mais c’est vraiment un langage passionnant et ça ouvre vraiment l’esprit sur d’autres façons de programmer.
Je ne suis pas sûr pour les choix de syntaxe des templates (pourquoi ne pas utiliser une vraie fonction Scala, utilisant la syntaxe XML déjà prête ?) mais ça va clairement dans le bon sens.
Je reste toujours sentimalement attaché à l’approche composant de Lift, mais ils ont aussi leurs problèmes…
Du coup, j’attends avec impatience que https://twitter.com/playframework/status/70580058540097536 se réalise (c’est peut-être déjà le cas, aucune idée). Ensuite seulement je pourrai sérieusement condidérer Play!.
@loic a noter que le trait Magic[T] (http://scala.playframework.org/documentation/scala-0.9.1/anorm#AddingsomeMagicT) permet d’avoir plusieurs méthodes associées à des requêtes select de base ainsi que l’update et le delete (cela ressemble ainsi beaucoup plus à la version Java) :
val c:Long = Country.count().single()
val c:List[Country] = Country.find().list()
val c:List[Country] = Country.find(« population > 1000000 »).list()
Ce qu’il faut aussi noter à propos de Scala en temps que moteur de template c’est que Scala apporte un gain de performance notable par rapport au moteur de template utilisant Groovy
Ne pas passer par un ORM peut clairement être justifié dans certain cas. Par contre écrire du SQL à la main sans support pour du refactoring simple du modèle est dommage. Que se passe t il si je veux changer le nom de ma table ou des mes colonnes?
Scala se vante de ses DSLs et de son typage fort… Par exemple, pour les templates HTML. Il devrait être possible d’utiliser quelque chose comme QueryDSL directement ou une version ‘Scala-isée’.
Je suis intéressé par tout retour d’expérience sur le sujet.
@bertrand tu peux regarder du côté de Squeryl http://squeryl.org/, qui est entre un ORM et un DSL pour SQL, mais tu as déjà tout le support des IDE puisque c’est du Scala tout bête.
Dans le même style, pour MongoDB, tu peux regarder ce qu’on peut faire avec Rogue https://github.com/foursquare/rogue . Vu que celui-ci repose sur Lift-Record, la communauté Play! s’est plutôt tournée vers Salat https://github.com/novus/salat . Mais je me suis laissé dire qu’il y aura bientôt un Query DSL à la Rogue dedans, mais ciblant les case class de Scala au lieu des objets Lift-Record 🙂
@Romain : ouep mais ça reste assez limité quand même, et ça ne résoud pas le problème des jointures à faire à la main…
My spouse and i felt absolutely relieved when Ervin managed to finish off his preliminary research through your ideas he got using your web pages. It is now and again perplexing to simply possibly be giving away tips and tricks others may have been selling. We really remember we have got the website owner to be grateful to because of that. The explanations you made, the straightforward site navigation, the relationships your site make it easier to promote – it’s all amazing, and it is helping our son and us reckon that the article is pleasurable, and that’s truly serious. Many thanks for everything!