Voyager avec Play 2

Nicolas Martignole
nicolas@touilleur-express.fr
@nmartignole
Scala.IO - 24/25 octobre 2013, Paris

sa...
Votre plan de vol

What
Why
How
ZapTravel
samedi 26 octobre 13
Avant de commencer...

Current	 status

samedi 26 octobre 13
ZapTravel

samedi 26 octobre 13
We search destinations & dates,
then find the best price, hotel and
transport, so you don’t need to

samedi 26 octobre 13
We search destinations & dates,
then find the best price, hotel and
transport, so you don’t need to

filter, map, reduce
sam...
ZapTravel
samedi 26 octobre 13
samedi 26 octobre 13
samedi 26 octobre 13
samedi 26 octobre 13
Mobile
API REST

samedi 26 octobre 13
samedi 26 octobre 13
samedi 26 octobre 13
samedi 26 octobre 13
samedi 26 octobre 13
Show me Romance
deals
samedi 26 octobre 13
Show me Family
deals
samedi 26 octobre 13
samedi 26 octobre 13
</zaptravel>
samedi 26 octobre 13
“
There are known knowns; there are things we know that we know.
There are known unknowns; that is to say, there are thing...
Aware

Don’t know

Know

Not aware
samedi 26 octobre 13
Aware

Don’t know

Know

Not aware
samedi 26 octobre 13
Quelques chiffres
• 159 000 hôtels
• 1383 destinations
• 840 000 transports (avions/trains)
• 1.4To images sur S3
• 20600 ...
redis 127.0.0.1:6379> hlen Hotel:Content:Short
(integer) 158 041

samedi 26 octobre 13
Ce que je savais

samedi 26 octobre 13
Ce que je savais
• Play! Framework

samedi 26 octobre 13
Ce que je savais
• Play! Framework
• Web development

samedi 26 octobre 13
Ce que je savais
• Play! Framework
• Web development
• Hiring and training developers

samedi 26 octobre 13
Ce que je savais
• Play! Framework
• Web development
• Hiring and training developers
• Kiss-ass project managment
samedi ...
Equipe
Ze Boss
Java
HTML CSS Scala
Play2 Scala

samedi 26 octobre 13
Equipe
Ze Boss
Java
HTML CSS Scala
Play2 Scala
5
3,75
2,5
1,25
0

samedi 26 octobre 13

Mai 2012

Ete 2012

Sept 2012 Oct ...
samedi 26 octobre 13
Comment
apprendre Scala
(et désapprendre Java)

samedi 26 octobre 13
Scala et Zaptravel
• Scala => recrutement
• Facile à apprendre
• Scala c’est simple

samedi 26 octobre 13
Ah tu fais du Scala

samedi 26 octobre 13
Paradigme objet ET fonctionnel

http://parleys.com/p/51c1994ae4b0d38b54f4621b

samedi 26 octobre 13
Ce que j’ai évité

- 18
samedi 26 octobre 13
SBT

samedi 26 octobre 13
SBT

ScalaZ
samedi 26 octobre 13
Les choses que je ne savais pas

• Faut être gonflé
• Communauté
• Parallélisme,
Reactivité

• Play2/Scala/Redis en
PROD ??...
samedi 26 octobre 13
Communauté Scala

Place de Scala, la communauté, par rapport à Java
samedi 26 octobre 13
Scala Days 2013 on parleys.com
94 516 VUEs

samedi 26 octobre 13
Parallélisme et
concurrence
It’s the web, stupid

Response[HTML] = Fx(Request)

samedi 26 octobre 13
Typesafe
•HTML template
•routes
•config
•LESS
samedi 26 octobre 13
Play2 -> Reactive
Reactive In Practice

samedi 26 octobre 13
Play2 -> Reactive
Reactive In Practice

samedi 26 octobre 13
Iteratee, Enumeratee and Enumerator
on Zaptravel

août - sept 2012
R.I.P

samedi 26 octobre 13
BusinessException
Iteratee/Enumeratee/Enumerator c’est cool, mais nous n’en n’avons pas besoin pour le moment.

samedi 26 ...
/**
* Server sent event streaming controller.
* Date: 06/08/12
* Time: 12:16
*/
object Streaming extends Controller {
  //...
Akka
faukon

samedi 26 octobre 13
Akka
• Cron Jobs
• Emails (Mailjet/Mailchimp)
• Facebook
• ElasticSearch index (proto)
• Sitemap
• Generate content (R.I.P...
Akka

samedi 26 octobre 13
Dev ?
Redis
Prices

read-only

Play2

Prix Hotels, Avions,
Voitures, Trains,
Rating Hotel

Redis

read/write

Static
Lieux...
Prod ?
CloudWatch

www

Route53

Cloudfront

redis prices

EC2 m2.2xlarge

ELB

redis static
EC2 c1.medium
S3
EC2 m2.xlarg...
Play2 + AWS

samedi 26 octobre 13
Idéal : c1.medium

2 vCPUs
1.7 GB mémoire

samedi 26 octobre 13
Redis

samedi 26 octobre 13
Ce qu’il faut retenir
2300 USD / mois

• IaaS versus PaaS pour Zaptravel

samedi 26 octobre 13
Boarding...

samedi 26 octobre 13
Des cas d’usages

samedi 26 octobre 13
Fonctionnalités

samedi 26 octobre 13
Fonctionnalités
• API REST

samedi 26 octobre 13
Fonctionnalités
• API REST
• Facebook

samedi 26 octobre 13
Fonctionnalités
• API REST
• Facebook
• Weather

samedi 26 octobre 13
Fonctionnalités
• API REST
• Facebook
• Weather
• GeoIP

samedi 26 octobre 13
Fonctionnalités
• API REST
• Facebook
• Weather
• GeoIP
• Semantic Search

samedi 26 octobre 13
Fonctionnalités
• API REST
• Facebook
• Weather
• GeoIP
• Semantic Search
• Cache Redis
samedi 26 octobre 13
Fonctionnalités
• API REST
• Facebook
• Weather
• GeoIP
• Semantic Search
• Cache Redis
samedi 26 octobre 13

• Mobile Web...
Fonctionnalités
• API REST
• Facebook
• Weather
• GeoIP
• Semantic Search
• Cache Redis
samedi 26 octobre 13

• Mobile Web...
Fonctionnalités
• API REST
• Facebook
• Weather
• GeoIP
• Semantic Search
• Cache Redis
samedi 26 octobre 13

• Mobile Web...
Fonctionnalités
• API REST
• Facebook
• Weather
• GeoIP
• Semantic Search
• Cache Redis
samedi 26 octobre 13

• Mobile Web...
Quelques exemples

ZapTravel
samedi 26 octobre 13
Charger une donnée venant de Redis

ZapTravel
samedi 26 octobre 13
Architecture
Redis
Air/Hotel/Cars/Ac

Web
HTTP
HTTPS

Web
LB

Redis
Resa/Users
Redis
Web Content

Web

ZapTravel
samedi 26...
Architecture
Redis

Web

Air/Hotel/Cars/Ac

Web
HTTP
HTTPS

Web

LB

Redis
Resa/Users
Redis
Web Content

redis

Web

ZapTr...
Cas d’usage
Donne moi le label qui correspond à originId

=380

ZapTravel
samedi 26 octobre 13
Cas d’usage
Donne moi le label qui correspond à originId

=380

def getSlug(originId: Long): Option[String] = Redis.pool.w...
Cas d’usage
Donne moi le label qui correspond à originId

=380

def getSlug(originId: Long): Option[String] = Redis.pool.w...
Cas d’usage
Donne moi le label qui correspond à originId

=380

def getSlug(originId: Long): Option[String] = Redis.pool.w...
Un mot sur les Tests

samedi 26 octobre 13
package models
 
import org.specs2.mutable._
 
import play.api.test._
import play.api.test.Helpers._
 
class OriginSpecs e...
Charger un objet

Charge moi un Objet «Londres»

ZapTravel
samedi 26 octobre 13
Charger un objet Origin
1) charger from-london
def getOrigin(originId: Long): Option[Origin] =
Redis.pool.withClient
{
cli...
Code smells
2) charger display...

def getOrigin(originId: Long): Option[Origin] =
Redis.pool.withClient {
client =>
Optio...
Cas d’usage
2) charger display...
def getOrigin(originId: Long): Option[Origin] = Redis.pool.withClient
{
client =>
for(sl...
La Tour Eiffel
1. Charger du JSON à partir de Redis
2. Interpréter et retourner un objet
PointOfInterest

ZapTravel
samedi...
HGET Pois:PoisHash 52511
{"name":"Eiffel
Tower","address":"","latitude":"48.8582493546","longitude":"2.2945117950","websit...
Play 2.1
• Définir une case class POI
• Définir un implicit Json.format[POI]
• C’est tout... ou presque

samedi 26 octobre 1...
Play 2.0
case class POI(name: String,
address: String,
latitude: String,
longitude: String,
website: Option[String],
photo...
Play 2.0

samedi 26 octobre 13
Play 2.1

samedi 26 octobre 13
Play 2.1
(Parser lorsque le JSON stocké sur Redis utilise une déclaration différente
de la case class)

samedi 26 octobre ...
Appel Redis et
interprétation JSON

samedi 26 octobre 13
Afficher une liste
ZapTravel
samedi 26 octobre 13
Afficher une liste

ZapTravel
samedi 26 octobre 13
Aller sur Redis
def allOrigins: List[Origin] =
Redis.pool.withClient {
client =>
// ...
// ...
}

Modèle
samedi 26 octobre...
Préparer une liste
def allUrlOrigins: Seq[(String, String)] =
{
Origin.allOrigins.map{
origin =>
(origin.slug, origin.labe...
Envoyer la liste au
template
Code dans la page HTML
<label for="location">Your travel origin is :</label>
@select( userFor...
Afficher une liste

ZapTravel
samedi 26 octobre 13
Gérer
l’authentification
samedi 26 octobre 13
Comment protéger l’accès à
une ressource ?

My Info

samedi 26 octobre 13
Comment protéger l’accès à
une ressource ?

My Info

samedi 26 octobre 13
Dans le Controller
object Application extends Controller
{
def index = Action {
implicit request =>
val username="test"
Ok...
Dans le Controller
object Application extends Controller
with Secured {
def index = ActionSecure {
username =>
implicit re...
trait Secured {
def username(request: RequestHeader) = request.session.get(Security.username)
def onUnauthorized(request: ...
Play2 et Sécurité
• Simple
• Composable
• Facile à tester

samedi 26 octobre 13
Optimiser
l’indexation et le
référencement

samedi 26 octobre 13
Indexation et
référencement
• URLs propres et pondérées
• Mots clés
• Liens et Sitemap
• Microformat (Hotel, Avion, Lieux)...
samedi 26 octobre 13
routes
Compilé et validé

samedi 26 octobre 13
URL
/from-boston/quality
GET
/$origin<from-(.*)>/:classifier
controllers.Frontoffice.home(origin:String, classifier: Strin...
Play2
• La séparation entre la partie routage et

la partie contrôleur permet de créer des
URLs «propres»

samedi 26 octob...
Sitemap
• Déclarer la table des matières de son
site

• Optimise le référencement
• Permet de mettre en cache les pages
cu...
samedi 26 octobre 13
Problème : construire le sitemap de
façon asynchrone

samedi 26 octobre 13
Solution : Async

Akka / Play2

samedi 26 octobre 13
def sitemap = Action {
implicit request =>
val longCall = Akka.future {
val today = ZapFormats.formatForJson(new DateTime(...
def sitemap = Action {
implicit request =>
val longCall = Akka.future {
val today = ... // some other code
val listOfSEOCa...
def sitemap = Action {
implicit request =>
val longCall = Akka.future {
val today = ZapFormats.formatForJson(new DateTime(...
def sitemap = Action {
implicit request =>
val longCall = Akka.future {
val today = ZapFormats.formatForJson(new DateTime(...
def sitemap = Action {
implicit request =>
val longCall = Akka.future {
val today = ZapFormats.formatForJson(new DateTime(...
Gestion du cache
samedi 26 octobre 13
Comment améliorer
les performances ?

samedi 26 octobre 13
Eviter de recharger la
même page,
utilisez code 304 NotModified
Note: @rosstuck a fait une session sur HTTP à Confoo mercr...
Exemple sur /from-paris/quality

Navigateur

Play2

GET /from-paris/quality

samedi 26 octobre 13
Exemple sur /from-paris/quality
Navigateur

Play2

OK

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
ETag: 112999...
Recharge /from-paris/quality
GET /from-paris/quality
If-None-Match: 112999307771
Navigateur

Play2

304 Not Modified
Conte...
Optimisation 1
• Evitez de faire travailler votre serveur
pour rien

• Déterminez des ETags «métiers»
• Attention à la ges...
Optimisation 2
Faire de la gestion de cache applicative

samedi 26 octobre 13
Cache applicatif ?

samedi 26 octobre 13
2 types de cache
Cache technique type
Varnish

- Process à part
- Cache HTTP

samedi 26 octobre 13

Cache de Play2 ou Redi...
2 types de cache
Cache technique type
Varnish

• Facile à installer
• Evite de solliciter
Play2

• Scalable
• Configurable
...
2 types de cache
Cache applicatif Play2/
Redis

• Prend en compte le
métier

• Permet de garder les

pages «authentifiées»
...
Sur Zaptravel
• Page d’accueil

optimisé avec Cache
de Play2

• Page Folio, section

top Deal avec cache
Play2

• Page Dea...
Et pour terminer

samedi 26 octobre 13
Merci

nicolas@touilleur-express.fr
@nmartignole
samedi 26 octobre 13
Upcoming SlideShare
Loading in...5
×

Voyager avec play scala

914

Published on

Retour d'expérience sur le développement d'une application avec Play2/Scala/Redis pendant un an chez Zaptravel.

Published in: Technology
1 Comment
2 Likes
Statistics
Notes
No Downloads
Views
Total Views
914
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
8
Comments
1
Likes
2
Embeds 0
No embeds

No notes for slide

Voyager avec play scala

  1. 1. Voyager avec Play 2 Nicolas Martignole nicolas@touilleur-express.fr @nmartignole Scala.IO - 24/25 octobre 2013, Paris samedi 26 octobre 13
  2. 2. Votre plan de vol What Why How ZapTravel samedi 26 octobre 13
  3. 3. Avant de commencer... Current status samedi 26 octobre 13
  4. 4. ZapTravel samedi 26 octobre 13
  5. 5. We search destinations & dates, then find the best price, hotel and transport, so you don’t need to samedi 26 octobre 13
  6. 6. We search destinations & dates, then find the best price, hotel and transport, so you don’t need to filter, map, reduce samedi 26 octobre 13
  7. 7. ZapTravel samedi 26 octobre 13
  8. 8. samedi 26 octobre 13
  9. 9. samedi 26 octobre 13
  10. 10. samedi 26 octobre 13
  11. 11. Mobile API REST samedi 26 octobre 13
  12. 12. samedi 26 octobre 13
  13. 13. samedi 26 octobre 13
  14. 14. samedi 26 octobre 13
  15. 15. samedi 26 octobre 13
  16. 16. Show me Romance deals samedi 26 octobre 13
  17. 17. Show me Family deals samedi 26 octobre 13
  18. 18. samedi 26 octobre 13
  19. 19. </zaptravel> samedi 26 octobre 13
  20. 20. “ There are known knowns; there are things we know that we know. There are known unknowns; that is to say, there are things that we now know we don't know. But there are also unknown unknowns – there are things we do not know we don't know. ” —United States Secretary of Defense, Donald Rumsfeld samedi 26 octobre 13
  21. 21. Aware Don’t know Know Not aware samedi 26 octobre 13
  22. 22. Aware Don’t know Know Not aware samedi 26 octobre 13
  23. 23. Quelques chiffres • 159 000 hôtels • 1383 destinations • 840 000 transports (avions/trains) • 1.4To images sur S3 • 20600 prix chambres hôtels samedi 26 octobre 13
  24. 24. redis 127.0.0.1:6379> hlen Hotel:Content:Short (integer) 158 041 samedi 26 octobre 13
  25. 25. Ce que je savais samedi 26 octobre 13
  26. 26. Ce que je savais • Play! Framework samedi 26 octobre 13
  27. 27. Ce que je savais • Play! Framework • Web development samedi 26 octobre 13
  28. 28. Ce que je savais • Play! Framework • Web development • Hiring and training developers samedi 26 octobre 13
  29. 29. Ce que je savais • Play! Framework • Web development • Hiring and training developers • Kiss-ass project managment samedi 26 octobre 13
  30. 30. Equipe Ze Boss Java HTML CSS Scala Play2 Scala samedi 26 octobre 13
  31. 31. Equipe Ze Boss Java HTML CSS Scala Play2 Scala 5 3,75 2,5 1,25 0 samedi 26 octobre 13 Mai 2012 Ete 2012 Sept 2012 Oct 2012 Nov 2012 Jan 2013 Oct 2013
  32. 32. samedi 26 octobre 13
  33. 33. Comment apprendre Scala (et désapprendre Java) samedi 26 octobre 13
  34. 34. Scala et Zaptravel • Scala => recrutement • Facile à apprendre • Scala c’est simple samedi 26 octobre 13
  35. 35. Ah tu fais du Scala samedi 26 octobre 13
  36. 36. Paradigme objet ET fonctionnel http://parleys.com/p/51c1994ae4b0d38b54f4621b samedi 26 octobre 13
  37. 37. Ce que j’ai évité - 18 samedi 26 octobre 13
  38. 38. SBT samedi 26 octobre 13
  39. 39. SBT ScalaZ samedi 26 octobre 13
  40. 40. Les choses que je ne savais pas • Faut être gonflé • Communauté • Parallélisme, Reactivité • Play2/Scala/Redis en PROD ??? • SEO samedi 26 octobre 13 • JSON+Redis • Typesafe / refactoring
  41. 41. samedi 26 octobre 13
  42. 42. Communauté Scala Place de Scala, la communauté, par rapport à Java samedi 26 octobre 13
  43. 43. Scala Days 2013 on parleys.com 94 516 VUEs samedi 26 octobre 13
  44. 44. Parallélisme et concurrence It’s the web, stupid Response[HTML] = Fx(Request) samedi 26 octobre 13
  45. 45. Typesafe •HTML template •routes •config •LESS samedi 26 octobre 13
  46. 46. Play2 -> Reactive Reactive In Practice samedi 26 octobre 13
  47. 47. Play2 -> Reactive Reactive In Practice samedi 26 octobre 13
  48. 48. Iteratee, Enumeratee and Enumerator on Zaptravel août - sept 2012 R.I.P samedi 26 octobre 13
  49. 49. BusinessException Iteratee/Enumeratee/Enumerator c’est cool, mais nous n’en n’avons pas besoin pour le moment. samedi 26 octobre 13
  50. 50. /** * Server sent event streaming controller. * Date: 06/08/12 * Time: 12:16 */ object Streaming extends Controller {   // Streaming using server sent event   def stream(requestId: String) = Action {     // Define an implicit EventNameExtractor wich extract the "event" name from the Json event so that the EventSource() sets the event in the message     implicit val eventNameExtractor: EventNameExtractor[JsValue]=EventNameExtractor[JsValue](eventName = (zepEvent)=>zepEvent. ("event").asOpt[String])         // Streams.events is a composition of HotelPrice and AirfarePrice.         Ok.feed(Streams.events(requestId) &> EventSource()).as("text/event-stream")   }  implicit val eventNameExtractor: EventNameExtractor[JsValue] = EventNameExtractor[JsValue](eventName = (zepEvent)=>zepEvent. ("event").asOpt[String]) samedi 26 octobre 13
  51. 51. Akka faukon samedi 26 octobre 13
  52. 52. Akka • Cron Jobs • Emails (Mailjet/Mailchimp) • Facebook • ElasticSearch index (proto) • Sitemap • Generate content (R.I.P.) samedi 26 octobre 13
  53. 53. Akka samedi 26 octobre 13
  54. 54. Dev ? Redis Prices read-only Play2 Prix Hotels, Avions, Voitures, Trains, Rating Hotel Redis read/write Static Lieux, Destinations, Contenu, Routage, URLs, Places, Tags, Webuser Redis slave-of EC2 m2.xlarge S3 samedi 26 octobre 13 1.2 GB 450k obj EC2 m2.2xlarge 27GB 870k obj
  55. 55. Prod ? CloudWatch www Route53 Cloudfront redis prices EC2 m2.2xlarge ELB redis static EC2 c1.medium S3 EC2 m2.xlarge logs SimpleDB samedi 26 octobre 13 redis backup
  56. 56. Play2 + AWS samedi 26 octobre 13
  57. 57. Idéal : c1.medium 2 vCPUs 1.7 GB mémoire samedi 26 octobre 13
  58. 58. Redis samedi 26 octobre 13
  59. 59. Ce qu’il faut retenir 2300 USD / mois • IaaS versus PaaS pour Zaptravel samedi 26 octobre 13
  60. 60. Boarding... samedi 26 octobre 13
  61. 61. Des cas d’usages samedi 26 octobre 13
  62. 62. Fonctionnalités samedi 26 octobre 13
  63. 63. Fonctionnalités • API REST samedi 26 octobre 13
  64. 64. Fonctionnalités • API REST • Facebook samedi 26 octobre 13
  65. 65. Fonctionnalités • API REST • Facebook • Weather samedi 26 octobre 13
  66. 66. Fonctionnalités • API REST • Facebook • Weather • GeoIP samedi 26 octobre 13
  67. 67. Fonctionnalités • API REST • Facebook • Weather • GeoIP • Semantic Search samedi 26 octobre 13
  68. 68. Fonctionnalités • API REST • Facebook • Weather • GeoIP • Semantic Search • Cache Redis samedi 26 octobre 13
  69. 69. Fonctionnalités • API REST • Facebook • Weather • GeoIP • Semantic Search • Cache Redis samedi 26 octobre 13 • Mobile Web version
  70. 70. Fonctionnalités • API REST • Facebook • Weather • GeoIP • Semantic Search • Cache Redis samedi 26 octobre 13 • Mobile Web version • Authentification
  71. 71. Fonctionnalités • API REST • Facebook • Weather • GeoIP • Semantic Search • Cache Redis samedi 26 octobre 13 • Mobile Web version • Authentification • Statistiques/Parcours visiteur
  72. 72. Fonctionnalités • API REST • Facebook • Weather • GeoIP • Semantic Search • Cache Redis samedi 26 octobre 13 • Mobile Web version • Authentification • Statistiques/Parcours visiteur
  73. 73. Quelques exemples ZapTravel samedi 26 octobre 13
  74. 74. Charger une donnée venant de Redis ZapTravel samedi 26 octobre 13
  75. 75. Architecture Redis Air/Hotel/Cars/Ac Web HTTP HTTPS Web LB Redis Resa/Users Redis Web Content Web ZapTravel samedi 26 octobre 13
  76. 76. Architecture Redis Web Air/Hotel/Cars/Ac Web HTTP HTTPS Web LB Redis Resa/Users Redis Web Content redis Web ZapTravel samedi 26 octobre 13
  77. 77. Cas d’usage Donne moi le label qui correspond à originId =380 ZapTravel samedi 26 octobre 13
  78. 78. 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)) } ZapTravel samedi 26 octobre 13
  79. 79. 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)) } ZapTravel samedi 26 octobre 13
  80. 80. 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 samedi 26 octobre 13 ZapTravel
  81. 81. Un mot sur les Tests samedi 26 octobre 13
  82. 82. 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/5064048 samedi 26 octobre 13
  83. 83. Charger un objet Charge moi un Objet «Londres» ZapTravel samedi 26 octobre 13
  84. 84. Charger un objet Origin 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 { .... ... } } } ZapTravel samedi 26 octobre 13
  85. 85. Code smells 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{ .... ... } } } ZapTravel samedi 26 octobre 13
  86. 86. 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 ZapTravel samedi 26 octobre 13
  87. 87. La Tour Eiffel 1. Charger du JSON à partir de Redis 2. Interpréter et retourner un objet PointOfInterest ZapTravel samedi 26 octobre 13
  88. 88. HGET Pois:PoisHash 52511 {"name":"Eiffel Tower","address":"","latitude":"48.8582493546","longitude":"2.2945117950","website":"www.toureiffel.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/famouslandmark/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"]} samedi 26 octobre 13
  89. 89. Play 2.1 • Définir une case class POI • Définir un implicit Json.format[POI] • C’est tout... ou presque samedi 26 octobre 13
  90. 90. 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 samedi 26 octobre 13
  91. 91. Play 2.0 samedi 26 octobre 13
  92. 92. Play 2.1 samedi 26 octobre 13
  93. 93. Play 2.1 (Parser lorsque le JSON stocké sur Redis utilise une déclaration différente de la case class) samedi 26 octobre 13
  94. 94. Appel Redis et interprétation JSON samedi 26 octobre 13
  95. 95. Afficher une liste ZapTravel samedi 26 octobre 13
  96. 96. Afficher une liste ZapTravel samedi 26 octobre 13
  97. 97. Aller sur Redis def allOrigins: List[Origin] = Redis.pool.withClient { client => // ... // ... } Modèle samedi 26 octobre 13
  98. 98. Préparer une liste def allUrlOrigins: Seq[(String, String)] = { Origin.allOrigins.map{ origin => (origin.slug, origin.label) }.sortBy(_._2) } Contrôleur samedi 26 octobre 13
  99. 99. 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 ) Vue samedi 26 octobre 13
  100. 100. Afficher une liste ZapTravel samedi 26 octobre 13
  101. 101. Gérer l’authentification samedi 26 octobre 13
  102. 102. Comment protéger l’accès à une ressource ? My Info samedi 26 octobre 13
  103. 103. Comment protéger l’accès à une ressource ? My Info samedi 26 octobre 13
  104. 104. Dans le Controller object Application extends Controller { def index = Action { implicit request => val username="test" Ok(html.index(username)) } } samedi 26 octobre 13
  105. 105. Dans le Controller object Application extends Controller with Secured { def index = ActionSecure { username => implicit request => Ok(html.index(username)) } } samedi 26 octobre 13
  106. 106. 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 samedi 26 octobre 13 Request[AnyContent] HTML
  107. 107. Play2 et Sécurité • Simple • Composable • Facile à tester samedi 26 octobre 13
  108. 108. Optimiser l’indexation et le référencement samedi 26 octobre 13
  109. 109. 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é samedi 26 octobre 13
  110. 110. samedi 26 octobre 13
  111. 111. routes Compilé et validé samedi 26 octobre 13
  112. 112. 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-greece samedi 26 octobre 13
  113. 113. Play2 • La séparation entre la partie routage et la partie contrôleur permet de créer des URLs «propres» samedi 26 octobre 13
  114. 114. 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 samedi 26 octobre 13
  115. 115. samedi 26 octobre 13
  116. 116. Problème : construire le sitemap de façon asynchrone samedi 26 octobre 13
  117. 117. Solution : Async Akka / Play2 samedi 26 octobre 13
  118. 118. 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 } } samedi 26 octobre 13
  119. 119. 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 } } samedi 26 octobre 13
  120. 120. 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 } } samedi 26 octobre 13
  121. 121. 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 } } samedi 26 octobre 13
  122. 122. 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) Bref... 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 } } curl http://www.zaptravel.com/sitemap.xml samedi 26 octobre 13
  123. 123. Gestion du cache samedi 26 octobre 13
  124. 124. Comment améliorer les performances ? samedi 26 octobre 13
  125. 125. Eviter de recharger la même page, utilisez code 304 NotModified Note: @rosstuck a fait une session sur HTTP à Confoo mercredi dernier samedi 26 octobre 13
  126. 126. Exemple sur /from-paris/quality Navigateur Play2 GET /from-paris/quality samedi 26 octobre 13
  127. 127. 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 erreur samedi 26 octobre 13
  128. 128. Recharge /from-paris/quality GET /from-paris/quality If-None-Match: 112999307771 Navigateur Play2 304 Not Modified Content-Length: 0 samedi 26 octobre 13
  129. 129. 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. samedi 26 octobre 13
  130. 130. Optimisation 2 Faire de la gestion de cache applicative samedi 26 octobre 13
  131. 131. Cache applicatif ? samedi 26 octobre 13
  132. 132. 2 types de cache Cache technique type Varnish - Process à part - Cache HTTP samedi 26 octobre 13 Cache de Play2 ou Redis - Code applicatif - utilise la mémoire de Play2 ou Redis
  133. 133. 2 types de cache Cache technique type Varnish • Facile à installer • Evite de solliciter Play2 • Scalable • Configurable samedi 26 octobre 13
  134. 134. 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 Varnish samedi 26 octobre 13
  135. 135. Sur Zaptravel • Page d’accueil optimisé avec Cache de Play2 • Page Folio, section top Deal avec cache Play2 • Page Deal, cache avec Redis samedi 26 octobre 13
  136. 136. Et pour terminer samedi 26 octobre 13
  137. 137. Merci nicolas@touilleur-express.fr @nmartignole samedi 26 octobre 13
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×