SlideShare a Scribd company logo
1 of 137
Download to read offline
Voyager avec Play 2

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

samedi 26 octobre 13
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
samedi 26 octobre 13
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 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
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 prix chambres hôtels
samedi 26 octobre 13
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 26 octobre 13
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 2012 Nov 2012

Jan 2013

Oct 2013
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 ???

• SEO
samedi 26 octobre 13

• JSON+Redis
• Typesafe / refactoring
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 octobre 13
/**
* 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
Akka
faukon

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

samedi 26 octobre 13
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
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
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 version
Fonctionnalités
• API REST
• Facebook
• Weather
• GeoIP
• Semantic Search
• Cache Redis
samedi 26 octobre 13

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

• Mobile Web version
• Authentification
• Statistiques/Parcours
visiteur
Fonctionnalités
• API REST
• Facebook
• Weather
• GeoIP
• Semantic Search
• Cache Redis
samedi 26 octobre 13

• Mobile Web version
• Authentification
• Statistiques/Parcours
visiteur
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 octobre 13
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
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.withClient
{
client =>
Option(client.hget("Url:From:Rev", originId.toString))
}

ZapTravel
samedi 26 octobre 13
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
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
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 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
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
{
client =>
Option(client.hget("Url:From:Rev", originId.toString)).map{
slug=>
Option(client.hget("Places:Place:"+originId, "display").map
{
.... ...
}
}
}

ZapTravel
samedi 26 octobre 13
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
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
La Tour Eiffel
1. Charger du JSON à partir de Redis
2. Interpréter et retourner un objet
PointOfInterest

ZapTravel
samedi 26 octobre 13
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
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
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
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 13
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 13
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
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
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(html.index(username))
}
}

samedi 26 octobre 13
Dans le Controller
object Application extends Controller
with Secured {
def index = ActionSecure {
username =>
implicit request =>
Ok(html.index(username))
}
}

samedi 26 octobre 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

}

String

samedi 26 octobre 13

Request[AnyContent]

HTML
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)
• Contenu non répété
samedi 26 octobre 13
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: 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
Play2
• La séparation entre la partie routage et

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

samedi 26 octobre 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
samedi 26 octobre 13
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())
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
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
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
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
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
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 mercredi dernier

samedi 26 octobre 13
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: 11299930771
Cache-Control: max-age=600, s-maxage=600, must-revalidate
Content-Length: 103586
...
...
ce n’est pas une erreur

samedi 26 octobre 13
Recharge /from-paris/quality
GET /from-paris/quality
If-None-Match: 112999307771
Navigateur

Play2

304 Not Modified
Content-Length: 0

samedi 26 octobre 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.

samedi 26 octobre 13
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 Redis

- Code applicatif
- utilise la mémoire
de Play2 ou Redis
2 types de cache
Cache technique type
Varnish

• Facile à installer
• Evite de solliciter
Play2

• Scalable
• Configurable
samedi 26 octobre 13
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
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
Et pour terminer

samedi 26 octobre 13
Merci

nicolas@touilleur-express.fr
@nmartignole
samedi 26 octobre 13

More Related Content

Similar to Voyager avec play scala

Grails from scratch to prod - MixIT 2011
Grails from scratch to prod - MixIT 2011Grails from scratch to prod - MixIT 2011
Grails from scratch to prod - MixIT 2011
Aurélien Maury
 

Similar to Voyager avec play scala (16)

la réalité mélangée de A a Z
la réalité mélangée de A a Zla réalité mélangée de A a Z
la réalité mélangée de A a Z
 
Stats web avec Hive chez Scoop.it
Stats web avec Hive chez Scoop.itStats web avec Hive chez Scoop.it
Stats web avec Hive chez Scoop.it
 
Ruby STAR
Ruby STARRuby STAR
Ruby STAR
 
Hands on lab Elasticsearch
Hands on lab ElasticsearchHands on lab Elasticsearch
Hands on lab Elasticsearch
 
I don't always write reactive application but when I do, it run on raspberry pi
I don't always write reactive application but when I do, it run on raspberry piI don't always write reactive application but when I do, it run on raspberry pi
I don't always write reactive application but when I do, it run on raspberry pi
 
Développer sereinement avec Node.js
Développer sereinement avec Node.jsDévelopper sereinement avec Node.js
Développer sereinement avec Node.js
 
Streams avec HighlandJS
Streams avec HighlandJSStreams avec HighlandJS
Streams avec HighlandJS
 
AngularJS et autres techno frontend
AngularJS et autres techno frontendAngularJS et autres techno frontend
AngularJS et autres techno frontend
 
Collab365 - Office 365 API & PowerShell : Le meilleur des deux mondes!
Collab365 - Office 365 API & PowerShell : Le meilleur des deux mondes!Collab365 - Office 365 API & PowerShell : Le meilleur des deux mondes!
Collab365 - Office 365 API & PowerShell : Le meilleur des deux mondes!
 
Estimation de projets Drupal
Estimation de projets DrupalEstimation de projets Drupal
Estimation de projets Drupal
 
4D Summit Europe 2016 - Conférence d'A&C Consulting : "Stocker des données su...
4D Summit Europe 2016 - Conférence d'A&C Consulting : "Stocker des données su...4D Summit Europe 2016 - Conférence d'A&C Consulting : "Stocker des données su...
4D Summit Europe 2016 - Conférence d'A&C Consulting : "Stocker des données su...
 
Performance des sites web - Latence - AFUP 2010
Performance des sites web - Latence - AFUP 2010Performance des sites web - Latence - AFUP 2010
Performance des sites web - Latence - AFUP 2010
 
Sass, Compass & Blueprint, ou les CSS à la cool
Sass, Compass & Blueprint, ou les CSS à la coolSass, Compass & Blueprint, ou les CSS à la cool
Sass, Compass & Blueprint, ou les CSS à la cool
 
Petit-déjeuner OCTO - Le Réactif
Petit-déjeuner OCTO - Le RéactifPetit-déjeuner OCTO - Le Réactif
Petit-déjeuner OCTO - Le Réactif
 
Normandy JUG - Elasticsearch
Normandy JUG - ElasticsearchNormandy JUG - Elasticsearch
Normandy JUG - Elasticsearch
 
Grails from scratch to prod - MixIT 2011
Grails from scratch to prod - MixIT 2011Grails from scratch to prod - MixIT 2011
Grails from scratch to prod - MixIT 2011
 

More from Nicolas Martignole

More from Nicolas Martignole (6)

Recettes, services et API pour vos équipes et vos développeurs
Recettes, services et API pour vos équipes et vos développeursRecettes, services et API pour vos équipes et vos développeurs
Recettes, services et API pour vos équipes et vos développeurs
 
Le Personal Branding pour les Développeurs (mais pas que...)
Le Personal Branding pour les Développeurs (mais pas que...)Le Personal Branding pour les Développeurs (mais pas que...)
Le Personal Branding pour les Développeurs (mais pas que...)
 
Devoxx Maroc 2015 HTTP 1, HTTP 2 and folks
Devoxx Maroc  2015 HTTP 1, HTTP 2 and folksDevoxx Maroc  2015 HTTP 1, HTTP 2 and folks
Devoxx Maroc 2015 HTTP 1, HTTP 2 and folks
 
Play! framework : Soft-Shake presentation
Play! framework : Soft-Shake presentationPlay! framework : Soft-Shake presentation
Play! framework : Soft-Shake presentation
 
Recettes d'une passion
Recettes d'une passionRecettes d'une passion
Recettes d'une passion
 
Usi2010 presentation nmartignole slideshare
Usi2010 presentation nmartignole slideshareUsi2010 presentation nmartignole slideshare
Usi2010 presentation nmartignole slideshare
 

Voyager avec play scala