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

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

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Ă©pondez y (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. ;)

blog comments powered by Disqus

Related posts