Your SlideShare is downloading. ×
Play2 ou l'architecture web réactive
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

Play2 ou l'architecture web réactive

889
views

Published on

Technical presentation about Zaptravel. …

Technical presentation about Zaptravel.
Confoo 2013 - February - Montreal - Canada

By Nicolas Martignole, Principal Engineer at Zaptravel.

Published in: Travel

0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
889
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
79
Comments
0
Likes
4
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. @nmartignole Nicolas Martignole Février 2013 ZapTravelvendredi 22 mars 13
  • 2. L’objectif de cette présentation est de... Découvrir Play2/Scalavendredi 22 mars 13
  • 3. ZapTravelvendredi 22 mars 13
  • 4. ZapTravelvendredi 22 mars 13
  • 5. vendredi 22 mars 13
  • 6. vendredi 22 mars 13
  • 7. ZapTravelvendredi 22 mars 13
  • 8. Framework Web ZapTravelvendredi 22 mars 13
  • 9. Java et Scala ZapTravelvendredi 22 mars 13
  • 10. Créé par Guillaume Bort @guillaumebort ZapTravelvendredi 22 mars 13
  • 11. Rails pour Java/Scala ZapTravelvendredi 22 mars 13
  • 12. Simple, productif Sauvegardez, rechargez, c’est tout ZapTravelvendredi 22 mars 13
  • 13. Communauté Java ZapTravelvendredi 22 mars 13
  • 14. vendredi 22 mars 13
  • 15. 2 Livres en préparationvendredi 22 mars 13
  • 16. Démonstration ZapTravelvendredi 22 mars 13
  • 17. Routes GET / Application.index ZapTravelvendredi 22 mars 13
  • 18. Scala def index() = Action { val name ="Nicolas" Ok(views.html.Application.index(name)) } ZapTravelvendredi 22 mars 13
  • 19. Scala - 2 @(name: String) @myTemplate() { <h1>Hello @name</h1> ... } ZapTravelvendredi 22 mars 13
  • 20. Play 2 en bref ZapTravelvendredi 22 mars 13
  • 21. Play 2 en bref • RESTful • Sécurité (XSS,CSRF) • Compilateur • Java NIO CoffeScript, Less • Driver asynchrone • JSON pour MongoDB • Websocket, Server • Require.js Sent Event, Comet • NoSQL et BigData ZapTravelvendredi 22 mars 13
  • 22. Quelques exemples ZapTravelvendredi 22 mars 13
  • 23. Charger une donnée venant de Redis ZapTravelvendredi 22 mars 13
  • 24. Architecture Redis Air/Hotel/Cars/Ac Redis Web Resa/Users Web Redis HTTP Web Content HTTPS LB Web ZapTravelvendredi 22 mars 13
  • 25. Architecture Redis Air/Hotel/Cars/Ac Web Redis Web Resa/Users Web Redis HTTP HTTPS redis Web Content LB Web ZapTravelvendredi 22 mars 13
  • 26. Cas d’usage Donne moi le label qui correspond à originId =380 ZapTravelvendredi 22 mars 13
  • 27. Cas d’usage 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)) } ZapTravelvendredi 22 mars 13
  • 28. Cas d’usage 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)) } ZapTravelvendredi 22 mars 13
  • 29. Cas d’usage 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/sedis ZapTravelvendredi 22 mars 13
  • 30. Un mot sur les Testsvendredi 22 mars 13
  • 31. 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 } } } } https://gist.github.com/nicmarti/5064048vendredi 22 mars 13
  • 32. Charger un objet Charge moi un Objet «Londres» ZapTravelvendredi 22 mars 13
  • 33. Charger un objet Origine 1) charger from-london 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 { .... ... } } } ZapTravelvendredi 22 mars 13
  • 34. Cas d’usage 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 { .... ... } } } ZapTravelvendredi 22 mars 13
  • 35. Cas d’usage 2) charger display... 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) } } for-comprehension https://gist.github.com/nicmarti/5064066 ZapTravelvendredi 22 mars 13
  • 36. La Tour Eiffel 1. Charger du JSON à partir de Redis 2. Interpréter et retourner un objet PointOfInterest ZapTravelvendredi 22 mars 13
  • 37. HGET Pois:PoisHash 52511 {"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":"Its 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"]}vendredi 22 mars 13
  • 38. 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 simplevendredi 22 mars 13
  • 39. 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 Eiffelvendredi 22 mars 13
  • 40. Play 2.0vendredi 22 mars 13
  • 41. Appel Redis et interprétation JSONvendredi 22 mars 13
  • 42. Afficher une liste ZapTravelvendredi 22 mars 13
  • 43. Afficher une liste ZapTravelvendredi 22 mars 13
  • 44. Aller sur Redis def allOrigins: List[Origin] = Redis.pool.withClient { client => // ... // ... } Modèlevendredi 22 mars 13
  • 45. Préparer une liste def allUrlOrigins: Seq[(String, String)] = { Origin.allOrigins.map{ origin => (origin.slug, origin.label) }.sortBy(_._2) } Contrôleurvendredi 22 mars 13
  • 46. Envoyer la liste au template Code dans la page HTML <label for="location">Your travel origin is :</label> @select( userForm("originCity"), FolioCriteria.allUrlOrigins , _label -> "Travel from origin", _showConstraints -> false ) Vuevendredi 22 mars 13
  • 47. Afficher une liste ZapTravelvendredi 22 mars 13
  • 48. Gérer l’authentificationvendredi 22 mars 13
  • 49. Comment protéger l’accès à une ressource ? My Infovendredi 22 mars 13
  • 50. Comment protéger l’accès à une ressource ? My Infovendredi 22 mars 13
  • 51. Dans le Controller object Application extends Controller { def index = Action { implicit request => val username="test" Ok(html.index(username)) } }vendredi 22 mars 13
  • 52. Dans le Controller object Application extends Controller with Secured { def index = ActionSecure { username => implicit request => Ok(html.index(username)) } }vendredi 22 mars 13
  • 53. 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 String Request[AnyContent] HTMLvendredi 22 mars 13
  • 54. Play2 et Sécurité • Simple • Composable • Facile à testervendredi 22 mars 13
  • 55. Optimiser l’indexation et le référencementvendredi 22 mars 13
  • 56. 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
  • 57. vendredi 22 mars 13
  • 58. routes Compilé et validévendredi 22 mars 13
  • 59. URL /from-boston/quality GET /$origin<from-(.*)>/:classifier controllers.Frontoffice.home(origin:String, classifier: String) http://www.zaptravel.com/romance/weekend-deals/from-paris/to-athens/ 12-Apr-2013-to-14-Apr-2013/elite-athens-greecevendredi 22 mars 13
  • 60. Play2 • La séparation entre la partie routage et la partie contrôleur permet de créer des URLs «propres»vendredi 22 mars 13
  • 61. 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.xmlvendredi 22 mars 13
  • 62. vendredi 22 mars 13
  • 63. Problème : construire le sitemap de façon asynchronevendredi 22 mars 13
  • 64. Solution : Async Akka / Play2vendredi 22 mars 13
  • 65. 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
  • 66. 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
  • 67. 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
  • 68. 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
  • 69. 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, Bref... 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") } curl http://www.zaptravel.com/sitemap.xml Async { implicit val timeout = akka.util.Timeout(60 seconds) longCall } }vendredi 22 mars 13
  • 70. Gestion du cachevendredi 22 mars 13
  • 71. Comment améliorer les performances ?vendredi 22 mars 13
  • 72. Eviter de recharger la même page, utilisez code 304 NotModified Note: @rosstuck a fait une session sur HTTP à Confoo mercredi derniervendredi 22 mars 13
  • 73. Exemple sur /from-paris/quality Navigateur Play2 GET /from-paris/qualityvendredi 22 mars 13
  • 74. Exemple sur /from-paris/quality Navigateur Play2 OK HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ETag: 11299930771 Cache-Control: max-age=600, s-maxage=600, must-revalidate Content-Length: 103586 ... ... ce n’est pas une erreurvendredi 22 mars 13
  • 75. Recharge /from-paris/quality GET /from-paris/quality If-None-Match: 112999307771 Navigateur Play2 304 Not Modified Content-Length: 0vendredi 22 mars 13
  • 76. 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
  • 77. Optimisation 2 Faire de la gestion de cache applicativevendredi 22 mars 13
  • 78. Cache applicatif ?vendredi 22 mars 13
  • 79. 2 types de cache Cache technique type Cache de Play2 ou Redis Varnish - Code applicatif - Process à part - utilise la mémoire - Cache HTTP de Play2 ou Redisvendredi 22 mars 13
  • 80. 2 types de cache Cache technique type Varnish • Facile à installer • Evite de solliciter Play2 • Scalable • Configurablevendredi 22 mars 13
  • 81. 2 types de cache Cache applicatif Play2/ Redis • Prend en compte le métier • Permet de garder les pages «authentifiées» • Pas aussi performant que la solution Varnishvendredi 22 mars 13
  • 82. Sur Zaptravel • Page d’accueil optimisé avec Cache de Play2 • Page Folio, section top Deal avec cache Play2 • Page Deal, cache avec Redisvendredi 22 mars 13
  • 83. Et pour terminer Play2 Architecture Web Apprentissage Tests unitaires Asynchrone (Enumeratee, Iteratee)vendredi 22 mars 13
  • 84. Merci https://joind.in/7951 @nmartignolevendredi 22 mars 13