Play2 ou l'architecture web réactive

  • View
    935

  • Download
    4

  • Category

    Travel

Preview:

DESCRIPTION

Technical presentation about Zaptravel. Confoo 2013 - February - Montreal - Canada By Nicolas Martignole, Principal Engineer at Zaptravel.

Citation preview

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

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

Recommended