Jâapprends Scala avec Finatra: Part I
Je suis obligĂ© de reconnaĂźtre que je âtrolleâ sur Scala sans rĂ©ellement en faire. Mais Ă chaque fois que jâai essayĂ© dans faire, je trouvais ça un peu obscur. Il y a peu @loic_d, connaissant mon intĂ©rĂȘt pour les micro-frameworks mâa poussĂ© ce lien http://finatra.info/. Finatra est un micro-framework web en Scala rĂ©alisĂ© chez Twitter. En lisant le code dâexemple sur la home page jâai trouvĂ© ça tout de suite trĂšs lisible.
class HelloWorld extends Controller {
get("/hello/:name") { request =>
val name = request.routeParams.getOrElse("name", "default user")
render.plain("hello " + name).toFuture
}
}
Tiens, on peut faire du code Scala pas ésotérique !?. En allant faire un tour ici Scala School (toujours chez Twitter, tiens tiens!), cela semble se confirmer.
Du coup (et vu que de grosses boutiques sây collent), je me suis dit âredonnons une chance Ă Scalaâ ⊠ou plutĂŽt, je me redonne une chance de comprendre ;).
Je vais donc juste faire un petit rappel sur la maniĂšre de faire son premier projet Scala et sa premiĂšre classe et ensuite nous passerons directement Ă la rĂ©alisation dâune application web avec Finatra avec un mini service json (mais mini mini).
Nous verrons aussi que nous pouvons facilement mettre en Ćuvre une fonctionnalitĂ© de rechargement et compilation automatique de lâapplication lorsque nous modifions le code.
Remarque: avez vous vu dans le code le mot clé toFuture
? Finatra est un framework web asynchrone.
Avertissement
Alors, attention, moi aussi jâapprends Scala (et Finatra), donc je peux dire des choses choquantes (ou peut-ĂȘtre fausses). Ce blog est propulsĂ© par GitHub par ici : https://github.com/k33g/k33g.github.com, ce qui veut dire que vous pouvez faire des pull requests sur ce que jâĂ©cris pour mâaider Ă mâamĂ©liorer (merci dâavance), vous pouvez mĂȘme dĂ©clarer des issues, mais je prĂ©fĂšre les PR (comme ça vous bossez pour moi ;) ). Par contre il vous faudra un compte GitHub (et câest lâoccasion de le faire tout de suite si vous nâen avez pas).
Pré-requis
- Vous devez installer Scala (facile)
- Vous devez installer sbt http://www.scala-sbt.org/release/docs/Getting-Started/Setup.html# installing-sbt (facile aussi)
Remarque: sbt pour Scala Build Tools est un utilitaire destiner Ă vous faciliter la vie pour vos projets Scala.
Rappel : Classes & Objets
Avant de rentrer dans le dur, nous allons juste un peu âdĂ©sacraliserâ Scala et apprendre Ă faire une petite classe, pour bien vĂ©rifier que finalement, câest simple.
Création du projet
Tout dâabord, nous allons crĂ©er une rapide structure de projet Scala, comprĂ©hensible par sbt, ce qui nous permettra de compiler notre code facilement.
CrĂ©ez lâarborescence suivante (adaptez selon vos besoins) : (lĂ on le fait Ă la main mais il existe des outils rassurez vous)
demo
|--src
|--main
|--scala
|--org
|--k33g
|--models
# Référence(s) pour aller plus loin sur la notion de création de projet
Programme principal
Dans demo/src/main/scala/org/k33g/
créez le fichier Demo.scala
(remarquez que le nom Demo
versus le nom du répertoire demo
, ce nâest pas obligatoire, mais câest plus propre) avec le code suivant :
package org.k33g
object Demo extends App {
println("Hello World!")
}
Sauvegardez, ouvrez votre terminal ou votre console, allez dans demo
(cd demo
) et tapez sbt run
(et validez). Ăa va mouliner un peu (Scala nâest pas super green au dĂ©marrage), pour finir par vous afficher un splendide :
Hello World!
\o/
First Class : Human
Dans demo/src/main/scala/org/k33g/models
créez le fichier Human.scala
avec le code suivant :
package org.k33g.models
class Human(val firstName: String, val lastName: String) {
def sayHello(): String = {
return "Hello " + firstName + " " + lastName
}
}
Nous venons juste de créer une classe Human
avec 2 âpropriĂ©tĂ©sâ firstName
et lastName
et une méthode sayHello
qui retourne une chaĂźne de caractĂšres. Ce nâest pas trop violent, et vous remarquez que la dĂ©finition des attributs de la classe par passage de paramĂštre au constructeur est assez Ă©lĂ©gante (je ne peux pas troller sur Scala constamment).
Modifiez ensuite le précédent fichier Demo.scala
:
package org.k33g
import org.k33g.models._
object Demo extends App {
var bob = new Human("Bob", "Morane")
println(bob.sayHello())
}
Toujours dans demo
lancez Ă nouveau un sbt run
, vous allez obtenir :
Hello Bob Morane
VoilĂ , vous savez tout ;), Scala câest facile ⊠On peut maintenant aller faire une application web en Scala (avec Finatra).
Ma premiĂšre application Finatra
Installer Finatra
- Téléchargez https://github.com/twitter/finatra/archive/1.5.2.zip
- DĂ©-zippez
- Mettez Ă jour votre
PATH
avec le chemin vers Finatra
Chez moi (sous OSX), cela ressemble à ça :
FINATRA_HOME=/Users/k33g_org/finatra-1.5.2
export FINATRA_HOME
export PATH=$PATH:$FINATRA_HOME
Générez votre 1er projet
- Tapez ceci :
finatra new org.k33g.DemoWeb
- A la question
Install Bower components? (y/n)
répondezy
(cela va permettre par défaut de télécharger Bootstrap et jQuery)
Vous obtenez donc votre squelette de projet dans le répertoire DemoWeb
. Mais avant de lancez quoique ce soit procédons à quelques réglages.
# PrĂ©parez votre âstack frontâ avec Bower : un peu de javascript
Allez dans DemoWeb
et modifiez bower.json
en lui ajoutant 2 dépendances : backbone
et underscore
, de cette maniĂšre :
{
"name": "DemoWeb",
"version": "0.0.1",
"license": "MIT",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"bootstrap" : "twbs/bootstrap",
"backbone" : null,
"underscore" : null
}
}
Ensuite, lancez bower update
(dans DemoWeb
) et les frameworks javascript Backbone et Underscore seront téléchargés.
# Modifiez (prĂ©parez) la page dâaccueil
Modifiez src/main/resources/public/index.html
de cette façon :
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<title>Demo</title>
<link rel="stylesheet" type="text/css" href="/components/bootstrap/dist/css/bootstrap.css">
</head>
<body>
<div class="container">
<header>
<h1>Je me la joue en Scala</h1>
</header>
</div>
<script src="/components/jquery/dist/jquery.js"></script>
<script src="/components/underscore/underscore.js"></script>
<script src="/components/backbone/backbone.js"></script>
</body>
</html>
# Modifiez le code de dĂ©marrage de lâapplication
Ouvrez src/main/scala/org/k33G/DemoWeb/App.scala
et simplifiez le code de cette maniĂšre :
package org.k33g.DemoWeb
import com.twitter.finatra._
import com.twitter.finatra.ContentType._
object App extends FinatraServer {
class MyApp extends Controller {
get("/") { request =>
render.static("index.html").toFuture
}
}
register(new MyApp())
}
Nous nous contentons donc de dire quâil faut afficher index.html
si lâon appelle la racine du site /
dans le navigateur.
# Il est temps de tester
Il suffit de lancer la commande sbt run
. La console sbt âva compilerâ votre application et ensuite vous notifier que vous pouvez vous connecter : finatra: http server started on port: :7070
, ouvrez donc http://localhost:7070/. Tout roule ? On passe donc Ă la suite. ArrĂȘtez lâapplication : Ctrl + c
.
CrĂ©ation dâun service json
Nous allons juste créer une classe Human
(encore!) et nous la âpublieronsâ au format json pour le navigateur.
Création de la classe Human
Tout dâabord crĂ©ez un rĂ©pertoire models
dans le répertoire src/main/scala/org/k33g
. Dans ce répertoire, créez une classe Human.scala
avec le code source suivant :
package org.k33g.models
class Human(val id: String, val firstName: String, val lastName: String) {
}
Création de notre service
Ouvrez Ă nouveau src/main/scala/org/k33g/DemoWeb/App.scala
, nous allons rajouter une âroutesâ qui fournira du jso, au navigateur :
PremiĂšrement, pensez Ă importer les modĂšles : import org.k33g.models._
et ajoutez ceci :
get("/humans/:id") { request =>
val id = request.routeParams.getOrElse("id", null)
val bob = new Human(id, "Bob", "Morane")
render.json(Map(
"id" -> bob.id,
"firstName" -> bob.firstName,
"lastName" -> bob.lastName
)).toFuture
}
Au final, vous aurez :
package org.k33g.DemoWeb
import com.twitter.finatra._
import com.twitter.finatra.ContentType._
import org.k33g.models._
object App extends FinatraServer {
class MyApp extends Controller {
get("/") { request =>
render.static("index.html").toFuture
}
get("/humans/:id") { request =>
val id = request.routeParams.getOrElse("id", null)
val bob = new Human(id, "Bob", "Morane")
render.json(Map(
"id" -> bob.id,
"firstName" -> bob.firstName,
"lastName" -> bob.lastName
)).toFuture
}
}
register(new MyApp())
}
Testez en lançant sbt run
et appelez http://localhost:7070/humans/42 et vous obtiendrez dans votre navigateur :
{"id":"42","firstName":"Bob","lastName":"Morane"}
Vous pouvez changer lâid dans lâurl pour tester
Pas trop dur?
# Remarque:
Pour les faignasses, sachez que vous pouvez remplacer :
render.json(Map(
"id" -> bob.id,
"firstName" -> bob.firstName,
"lastName" -> bob.lastName
)).toFuture
par :
render.json(bob).toFuture
Utilisation du service json avec Backbone
Parce quâune application web sans javascript nâest pas une vraie application web, retournez ouvrir index.html
et ajouter le code suivant en vas de page juste avant la fermeture de la balise <body>
(donc </body>
) :
<script>
var HumanModel = Backbone.Model.extend({
urlRoot : "humans"
});
$(function() {
var bob = new HumanModel({id:"42"})
bob.fetch()
.done(function(){
$("h1").html(bob.get("firstName") + " " + bob.get("lastName"));
})
});
</script>
Rafraßchissez votre page, vous allez voir que votre titre se met à jour avec les données du service json.
Auto-Reload
Ce qui me plaisez Ă©normĂ©ment dans Play!>1 (et 2), câĂ©tait le rechargement automatique (+compilation) lorsque lâon modifiait le code. Sachez que moyennant une petite âbidouilleâ ceci est trĂšs possible. Il suffit dâajouter un plugin Ă sbt. Ce plugin est sbt-revolver.
Commencez par quitter la console sbt (Ctrl + c
). Ensuite dans le répertoire project
de votre application, créez un fichier plugins.sbt
avec le contenu suivant :
addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2")
puis Ă la racine de votre projet, dans le fichier build.sbt
ajoutez la ligne Revolver.settings
comme ceci :
name := "DemoWeb"
version := "0.0.1-SNAPSHOT"
scalaVersion := "2.10.3"
libraryDependencies ++= Seq(
"com.twitter" %% "finatra" % "1.5.2"
)
Revolver.settings
resolvers +=
"Twitter" at "http://maven.twttr.com"
Relancez mais avec la commande duivante sbt ~re-start
(qui va charger les dépendances nécessaires puis lancer votre application).
Maintenant Ă chaque fois que vous modifierez le code, sbt recompilera et relancera lâapplication.
Essayez ça par exemple : dans App.scala
get("/humans") { request =>
val bob = new Human("42", "Bob", "Morane")
val john = new Human("00", "John", "Doe")
render.json(Array(bob, john)).toFuture
}
Sauvegardez, regardez votre console qui compile automatiquement, puis appelez http://localhost:7070/humans et vous obtenez :
[{"id":"42","firstName":"Bob","lastName":"Morane"},{"id":"00","firstName":"John","lastName":"Doe"}]
Magique!
Vous avez donc tout ce quâil faut pour tester un peu tout ça et la prochaine fois on fait la suite des services avec un peu de base de donnĂ©es.
Enjoy! (et bon WE pluvieux)
Et encore merci @loic_d, jâai fini par mây mettre. ;)
Tweet