Play2 ou l'architecture web réactive
-
Upload
nicolas-martignole -
Category
Travel
-
view
938 -
download
4
description
Transcript of Play2 ou l'architecture web réactive
ZapTravel
@nmartignoleNicolas Martignole
Février 2013
vendredi 22 mars 13
Découvrir Play2/Scala
L’objectif de cette présentation est de...
vendredi 22 mars 13
ZapTravel
vendredi 22 mars 13
ZapTravel
vendredi 22 mars 13
vendredi 22 mars 13
vendredi 22 mars 13
ZapTravel
vendredi 22 mars 13
Framework Web
ZapTravel
vendredi 22 mars 13
Java et Scala
ZapTravel
vendredi 22 mars 13
Créé par Guillaume Bort
@guillaumebort
ZapTravel
vendredi 22 mars 13
Rails pour Java/Scala
ZapTravel
vendredi 22 mars 13
Simple, productif
ZapTravel
Sauvegardez, rechargez, c’est tout
vendredi 22 mars 13
Communauté Java
ZapTravel
vendredi 22 mars 13
vendredi 22 mars 13
2 Livres en préparation
vendredi 22 mars 13
Démonstration
ZapTravel
vendredi 22 mars 13
Routes
GET / Application.index
ZapTravel
vendredi 22 mars 13
Scala
def index() = Action { val name ="Nicolas" Ok(views.html.Application.index(name)) }
ZapTravel
vendredi 22 mars 13
Scala - 2@(name: String)
@myTemplate() {
<h1>Hello @name</h1> ... }
ZapTravel
vendredi 22 mars 13
Play 2 en bref
ZapTravel
vendredi 22 mars 13
Play 2 en bref• RESTful
• Compilateur CoffeScript, Less
• JSON
• Websocket, Server Sent Event, Comet
• NoSQL et BigData
• Sécurité (XSS,CSRF)
• Java NIO
• Driver asynchrone pour MongoDB
• Require.js
ZapTravel
vendredi 22 mars 13
Quelques exemples
ZapTravel
vendredi 22 mars 13
Charger une donnée venant de Redis
ZapTravel
vendredi 22 mars 13
Architecture
LB
Web
Web
Web
HTTPHTTPS
RedisAir/Hotel/Cars/Ac
RedisResa/Users
RedisWeb Content
ZapTravel
vendredi 22 mars 13
Architecture
LB
Web
Web
Web
HTTPHTTPS
RedisAir/Hotel/Cars/Ac
RedisResa/Users
RedisWeb Content
Web
redis
ZapTravel
vendredi 22 mars 13
Cas d’usage
ZapTravel
Donne moi le label qui correspond à originId =380
vendredi 22 mars 13
Cas d’usage
ZapTravel
Donne moi le label qui correspond à originId =380
def getSlug(originId: Long): Option[String] = Redis.pool.withClient { client => Option(client.hget("Url:From:Rev", originId.toString))}
vendredi 22 mars 13
Cas d’usage
ZapTravel
Donne moi le label qui correspond à originId =380
def getSlug(originId: Long): Option[String] = Redis.pool.withClient { client => Option(client.hget("Url:From:Rev", originId.toString))}
vendredi 22 mars 13
Cas d’usage
ZapTravel
Donne moi le label qui correspond à originId =380
def getSlug(originId: Long): Option[String] = Redis.pool.withClient { client => Option(client.hget("Url:From:Rev", originId.toString))}
Driver Sedis https://github.com/pk11/sedisvendredi 22 mars 13
Un mot sur les Tests
vendredi 22 mars 13
https://gist.github.com/nicmarti/5064048
package models import org.specs2.mutable._ import play.api.test._import play.api.test.Helpers._ class OriginSpecs extends Specification { "An Origin" should { "returns the slug for a valid origin" in { running(FakeApplication()) { Origin.getSlug(380) mustEqual Some("from-london") Origin.getSlug(1) mustEqual Some("from-paris") Origin.getSlug(-9999) mustEqual None } } }}
vendredi 22 mars 13
Charger un objet
ZapTravel
Charge moi un Objet «Londres»
vendredi 22 mars 13
Charger un objet Origine
ZapTravel
def getOrigin(originId: Long): Option[Origin] = Redis.pool.withClient { client => Option(client.hget("Url:From:Rev", originId.toString)).map{ slug=> Option(client.hget("Places:Place:"+originId, "display").map { .... ... } }}
1) charger from-london
vendredi 22 mars 13
Cas d’usage
ZapTravel
2) charger display...
def getOrigin(originId: Long): Option[Origin] = Redis.pool.withClient { client => Option(client.hget("Url:From:Rev", originId.toString)).map{ slug=> Option(client.hget("Places:Place:"+originId, "display").map { .... ... } }}
vendredi 22 mars 13
Cas d’usage
ZapTravel
def getOrigin(originId: Long): Option[Origin] = Redis.pool.withClient { client => for(slug<-Option(client.hget("Url:From:Rev", originId.toString)); display<-Option(client.hget("Places:Place:"+originId, "display") )) yield Origin(originId,display,slug)
}}
2) charger display...
for-comprehensionhttps://gist.github.com/nicmarti/5064066
vendredi 22 mars 13
La Tour Eiffel
ZapTravel
1. Charger du JSON à partir de Redis2. Interpréter et retourner un objet PointOfInterest
vendredi 22 mars 13
{"name":"Eiffel Tower","address":"","latitude":"48.8582493546","longitude":"2.2945117950","website":"www.tour-eiffel.fr","rank":3,"photo":{"r":"eiffel-tower-paris-france","k":"6b56","e":"jpg","w":2406,"h":1600,"a":"Mirari Erdoiza","l":"http:\\/\\/www.fotopedia.com\\/items\\/anboto-RiKxAA3gE6I"},"sentences":{"gbs":[{"d":"The Eiffel Tower is one of the most famous monuments in the world (324 metres, 10,100 tonnes).","a":"Paris","l":"http:\\/\\/www.paris.com\\/paris_landmarks\\/monuments\\/eiffel_tower_paris"},{"d":"This is without doubt one of the most recognizable structures in the world.","a":"Frommers","l":"http:\\/\\/www.frommers.com\\/destinations\\/paris\\/A25288.html"},{"d":"If the Statue of Liberty is emblematic of New York, Big Ben is London, and the Kremlin is Moscow, then the Eiffel Tower is the symbol of Paris.","a":"Fodors","l":"http:\\/\\/www.fodors.com\\/world\\/europe\\/france\\/paris\\/review-97417.html"},{"d":"When it was built for the 1889 Exposition Universelle (World Fair), marking the centenary of the Revolution, the Tour Eiffel faced massive opposition from Paris' artistic and literary elite.","a":"Lonely Planet","l":"http:\\/\\/www.lonelyplanet.com\\/france\\/paris\\/sights\\/famous-landmark\\/eiffel-tower"}],"tips":[{"d":"It's pretty high!.","a":"annawelford","l":"http:\\/\\/www.lonelyplanet.com\\/france\\/paris\\/sights\\/famous-landmark\\/eiffel-tower","s":"Lonely Planet"},{"d":"Bigger than you think.","a":"anomolly","l":"http:\\/\\/www.lonelyplanet.com\\/france\\/paris\\/sights\\/famous-landmark\\/eiffel-tower","s":"Lonely Planet"},{"d":"Overcrowded.","a":"anshjain","l":"http:\\/\\/www.lonelyplanet.com\\/france\\/paris\\/sights\\/famous-landmark\\/eiffel-tower","s":"Lonely Planet"},{"d":"The restaurant on the first floor is an amazing experience!.","a":"ansofie","l":"http:\\/\\/www.lonelyplanet.com\\/france\\/paris\\/sights\\/famous-landmark\\/eiffel-tower","s":"Lonely Planet"}]},"tags":["Landmark","Memorials\\/Monuments","Sights","Famous landmark"]}
HGET Pois:PoisHash 52511
vendredi 22 mars 13
Play 2.0
• Définir une case class POI
• Définir un Format[POI]
• Ecrire la fonction pour lire et parser le JSON
Note : Play 2.1 apporte un nouveau parser JSON plus simple
vendredi 22 mars 13
Play 2.0
case class POI(name: String, address: String, latitude: String, longitude: String, website: Option[String], photo: Option[SightPhoto] = None, sentences: Sentences, tags: Option[List[String]])
POI = Point of Interest = notre Tour Eiffel
vendredi 22 mars 13
Play 2.0
vendredi 22 mars 13
Appel Redis et interprétation JSON
vendredi 22 mars 13
Afficher une listeZapTravel
vendredi 22 mars 13
Afficher une liste
ZapTravel
vendredi 22 mars 13
Aller sur Redisdef allOrigins: List[Origin] = Redis.pool.withClient { client => // ... // ... }
Modèlevendredi 22 mars 13
Préparer une listedef allUrlOrigins: Seq[(String, String)] = { Origin.allOrigins.map{ origin => (origin.slug, origin.label) }.sortBy(_._2)}
Contrôleurvendredi 22 mars 13
Envoyer la liste au template
<label for="location">Your travel origin is :</label>
@select( userForm("originCity"),
FolioCriteria.allUrlOrigins , '_label -> "Travel from origin", '_showConstraints -> false)
Code dans la page HTML
Vuevendredi 22 mars 13
Afficher une liste
ZapTravel
vendredi 22 mars 13
Gérer l’authentification
vendredi 22 mars 13
Comment protéger l’accès à une ressource ?
My Info
vendredi 22 mars 13
Comment protéger l’accès à une ressource ?
My Info
vendredi 22 mars 13
Dans le Controllerobject Application extends Controller { def index = Action { implicit request => val username="test" Ok(html.index(username)) }
}
vendredi 22 mars 13
Dans le Controllerobject Application extends Controller with Secured { def index = ActionSecure { username => implicit request => Ok(html.index(username)) }
}
vendredi 22 mars 13
trait Secured {
def username(request: RequestHeader) = request.session.get(Security.username)
def onUnauthorized(request: RequestHeader) = Results.Redirect(routes.Auth.login)
def ActionSecure(f: => String => Request[AnyContent] => Result) = { Security.Authenticated(username, onUnauthorized) { user => Action{ request => f(user)(request) } } }
} Result
HTMLString Request[AnyContent]
vendredi 22 mars 13
Play2 et Sécurité
• Simple
• Composable
• Facile à tester
vendredi 22 mars 13
Optimiser l’indexation et le référencement
vendredi 22 mars 13
Indexation et référencement
• URLs propres et pondérées
• Mots clés
• Liens et Sitemap
• Microformat (Hotel, Avion, Lieux)
• Contenu non répété
vendredi 22 mars 13
vendredi 22 mars 13
routes
Compilé et validé
vendredi 22 mars 13
URL
GET /$origin<from-(.*)>/:classifier controllers.Frontoffice.home(origin:String, classifier: String)
/from-boston/quality
http://www.zaptravel.com/romance/weekend-deals/from-paris/to-athens/12-Apr-2013-to-14-Apr-2013/elite-athens-greece
vendredi 22 mars 13
Play2
• La séparation entre la partie routage et la partie contrôleur permet de créer des URLs «propres»
vendredi 22 mars 13
Sitemap
• Déclarer la table des matières de son site
• Optimise le référencement
• Permet de mettre en cache les pages
curl http://www.zaptravel.com/sitemap.xml
vendredi 22 mars 13
vendredi 22 mars 13
Problème : construire le sitemap de façon asynchrone
vendredi 22 mars 13
Solution : Async
Akka / Play2
vendredi 22 mars 13
def sitemap = Action { implicit request => val longCall = Akka.future { val today = ZapFormats.formatForJson(new DateTime()) val listOfSEOCards = FolioCard.allForSEO().flatMap { seo => val localURL = routes.Frontoffice.showFolioDynamically(seo.category, seo.duration, seo.origin, seo.destination, seo.dateRange, seo.segment).absoluteURL(secure = false) val updatedUrl = (for (l <- localAddress; p <- publicAddress) yield localURL.replaceAll(l, p)) updatedUrl } Ok(views.xml.Application.sitemap(today, listOfSEOCards)).as("text/xml") } Async { implicit val timeout = akka.util.Timeout(60 seconds) longCall } }
vendredi 22 mars 13
def sitemap = Action { implicit request => val longCall = Akka.future { val today = ... // some other code val listOfSEOCards = FolioCard.allForSEO().flatMap { seo => val localURL = routes.Frontoffice.showFolioDynamically(seo.category, seo.duration, seo.origin, seo.destination, seo.dateRange, seo.segment).absoluteURL(secure = false) val updatedUrl = (for (l <- localAddress; p <- publicAddress) yield localURL.replaceAll(l, p)) updatedUrl } Ok(views.xml.Application.sitemap(today, listOfSEOCards) ).as("text/xml") } Async { implicit val timeout = akka.util.Timeout(60 seconds) longCall } }
vendredi 22 mars 13
def sitemap = Action { implicit request => val longCall = Akka.future { val today = ZapFormats.formatForJson(new DateTime()) val listOfSEOCards = FolioCard.allForSEO().flatMap { seo => val localURL = routes.Frontoffice.showFolioDynamically(seo.category, seo.duration, seo.origin, seo.destination, seo.dateRange, seo.segment).absoluteURL(secure = false)
val updatedUrl = (for (l <- localAddress; p <- publicAddress) yield localURL.replaceAll(l, p))
updatedUrl } Ok(views.xml.Application.sitemap(today, listOfSEOCards)).as("text/xml") } Async { implicit val timeout = akka.util.Timeout(60 seconds) longCall } }
vendredi 22 mars 13
def sitemap = Action { implicit request => val longCall = Akka.future { val today = ZapFormats.formatForJson(new DateTime()) val listOfSEOCards = FolioCard.allForSEO().flatMap { seo => val localURL = routes.Frontoffice.showFolioDynamically(seo.category, seo.duration, seo.origin, seo.destination, seo.dateRange, seo.segment).absoluteURL(secure = false)
val updatedUrl = (for (l <- localAddress; p <- publicAddress) yield localURL.replaceAll(l, p))
updatedUrl } Ok(views.xml.Application.sitemap(today, listOfSEOCards)).as("text/xml") } Async { implicit val timeout = akka.util.Timeout(60 seconds) longCall } }
vendredi 22 mars 13
def sitemap = Action { implicit request => val longCall = Akka.future { val today = ZapFormats.formatForJson(new DateTime()) val listOfSEOCards = FolioCard.allForSEO().flatMap { seo => val localURL = routes.Frontoffice.showFolioDynamically(seo.category, seo.duration, seo.origin, seo.destination, seo.dateRange, seo.segment).absoluteURL(secure = false)
val updatedUrl = (for (l <- localAddress; p <- publicAddress) yield localURL.replaceAll(l, p))
updatedUrl } Ok(views.xml.Application.sitemap(today, listOfSEOCards)).as("text/xml") } Async { implicit val timeout = akka.util.Timeout(60 seconds) longCall } }
Bref...curl http://www.zaptravel.com/sitemap.xml
vendredi 22 mars 13
Gestion du cachevendredi 22 mars 13
Comment améliorer les performances ?
vendredi 22 mars 13
Eviter de recharger la même page,
utilisez code 304 NotModified
Note: @rosstuck a fait une session sur HTTP à Confoo mercredi dernier
vendredi 22 mars 13
Exemple sur /from-paris/quality
Navigateur Play2
GET /from-paris/quality
vendredi 22 mars 13
Exemple sur /from-paris/qualityNavigateur Play2
OK
HTTP/1.1 200 OKContent-Type: text/html; charset=utf-8ETag: 11299930771Cache-Control: max-age=600, s-maxage=600, must-revalidateContent-Length: 103586......
ce n’est pas une erreur
vendredi 22 mars 13
Recharge /from-paris/quality
Navigateur Play2
GET /from-paris/qualityIf-None-Match: 112999307771
304 Not ModifiedContent-Length: 0
vendredi 22 mars 13
Optimisation 1
• Evitez de faire travailler votre serveur pour rien
• Déterminez des ETags «métiers»
• Attention à la gestion du cache et des serveurs mandataires.
vendredi 22 mars 13
Optimisation 2
Faire de la gestion de cache applicative
vendredi 22 mars 13
Cache applicatif ?
vendredi 22 mars 13
2 types de cacheCache technique type Varnish
Cache de Play2 ou Redis
- Process à part- Cache HTTP
- Code applicatif- utilise la mémoire de Play2 ou Redis
vendredi 22 mars 13
2 types de cache
• Facile à installer
• Evite de solliciter Play2
• Scalable
• Configurable
Cache technique type Varnish
vendredi 22 mars 13
2 types de cache
• Prend en compte le métier
• Permet de garder les pages «authentifiées»
• Pas aussi performant que la solution Varnish
Cache applicatif Play2/Redis
vendredi 22 mars 13
Sur Zaptravel• Page d’accueil
optimisé avec Cache de Play2
• Page Folio, section top Deal avec cache Play2
• Page Deal, cache avec Redis
vendredi 22 mars 13
Et pour terminer
Play2Architecture WebApprentissageTests unitairesAsynchrone (Enumeratee, Iteratee)
vendredi 22 mars 13
Merci
https://joind.in/7951
@nmartignole
vendredi 22 mars 13