SlideShare uma empresa Scribd logo
1 de 47
Baixar para ler offline
Finatra v2
Fast, testable Scala services
Steve Cosenza @scosenza
Finatra & Data API Tech Lead @twitter
What is Finatra?
• Finatra is a framework for easily building API services on top of
Twitter’s Scala stack (twitter-server, finagle, twitter-util)
• Currently supports building HTTP services
• Plan to support building Thrift services
Project History
• Feb 2012: Finatra project started on Github by two Twitter engineers
(Julio Capote and Chris Burnett)
• June 2014: Christopher Coco and Steve Cosenza on the Data API
team release Finatra v2.0.0-SNAPSHOT internally at Twitter
• Jan 2015: Finatra v1.6 released for Scala 2.9 and 2.10
• April 2015: Finatra v2.0.0M1 publicly released for Scala 2.10 and 2.11
Highlights
• Production use as Twitter’s HTTP framework
• ~50 times faster than v1.6 in several benchmarks
• Powerful feature and integration test support
• JSR-330 Dependency Injection using Google Guice
• Jackson based JSON parsing supporting required fields, default values,
and custom validations
• Logback MDC integration for contextual logging across Futures
• Guice request scope integration with Futures
Twitter Inject Libraries
• inject-core
• inject-app
• inject-server
• inject-modules
• inject-thrift-client
• inject-request-scope
Finatra Libraries
• finatra-http
• finatra-jackson
• finatra-logback
• finatra-httpclient
• finatra-utils
Tutorial
Let’s create a simple HTTP API for Tweets :-)
Create a FeatureTest
class TwitterCloneFeatureTest extends Test {


val server = new EmbeddedHttpServer(

twitterServer = new HttpServer {})



"Post tweet" in {

server.httpPost(

path = "/tweet",

postBody = """

{

"message": "Hello #SFScala",

"location": {

"lat": "37.7821120598956",

"long": "-122.400612831116"

},

"sensitive": false

}”"",
andExpect = Created,
withLocationHeader = "/tweet/123")

}

}
And let’s assert the response body
"Post tweet" in {

server.httpPost(

path = "/tweet",

postBody = """

{

"message": "Hello #SFScala",

"location": {...}
"sensitive": false

}”"",

andExpect = Created,

withLocationHeader = "/tweet/123",

withJsonBody = """

{

"id": "123",

"message": "Hello #SFScala",

"location": {

"lat": "37.7821120598956",

"long": "-122.400612831116"

},

"sensitive": false

}""")

}
Test Failure: Route not found
===========================================================================
HTTP POST /tweet
[Header] Content-Length -> 204
[Header] Content-Type -> application/json;charset=utf-8
[Header] Host -> 127.0.0.1:60102
{
"message" : "Hello #SFScala",
"location" : {
"lat" : "37.7821120598956",
"long" : "-122.400612831116"
},
"sensitive" : false
}
===========================================================================
RoutingService Request("POST /tweet", from /127.0.0.1:60103) not found in
registered routes:
---------------------------------------------------------------------------
[Status] 404 Not Found
[Header] Content-Length -> 0
*EmptyBody*
Create a Controller
package finatra.quickstart.controllers



import com.twitter.finagle.http.Request

import com.twitter.finatra.http.Controller



class TweetsController extends Controller {



post("/tweet") { request: Request =>

response.created("hello")

}

}
Create a Server
import com.twitter.finatra.HttpServer

import com.twitter.finatra.filters.CommonFilters

import com.twitter.finatra.routing.HttpRouter

import finatra.quickstart.controllers.TweetsControllerV1



class TwitterCloneServer extends HttpServer {



override def configureHttp(router: HttpRouter): Unit = {

router

.filter[CommonFilters]

.add(new TweetsController)

}

}
Add Server to FeatureTest
class TwitterCloneFeatureTest extends Test {


val server = new EmbeddedHttpServer(

twitterServer = new TwitterCloneServer)



...
}
Test Failure: Unexpected response body
===========================================================================
HTTP POST /tweet
[Header] Content-Length -> 204
[Header] Content-Type -> application/json;charset=utf-8
[Header] Host -> 127.0.0.1:60118
...
===========================================================================
127.0.0.1 - - [24/Apr/2015:21:36:25 +0000] "POST /tweet HTTP/1.1" 201 5 9 "-"
---------------------------------------------------------------------------
[Status] 201 Created
[Header] Content-Length -> 5
hello
Unrecognized token 'hello': was expecting ('true', 'false' or 'null')
at [Source: hello; line: 1, column: 11]
JSON DIFF FAILED!
*
Received: hello
Expected: {“id":"123","location":{"lat":"37.7821120598956","long":"-122.4006 ...
Let’s parse some JSON
{

"message": "Hello #SFScala",

"location": {

"lat": "37.7821120598956",

"long": "-122.400612831116"

},

"sensitive": false

}
case class PostedTweet(

message: String,

location: Option[Location],

sensitive: Boolean = false) {



def toStatus(id: StatusId) = { ... }

}
case class Location(

lat: Double,

long: Double)

Update Controller to use PostedTweet
class TweetsController extends Controller {



post("/tweet") { postedTweet: PostedTweet =>

response.created(postedTweet)

}

}

Test failure: ’id’ field in body missing
[Status] 201 Created
[Header] Content-Type -> application/json; charset=utf-8
[Header] Content-Length -> 107
{
"message" : "Hello #SFScala",
"location" : {
"lat" : 37.7821120598956,
"long" : -122.400612831116
},
"sensitive" : false
}
JSON DIFF FAILED!
*
Received: {“location”:{"lat":37.7821120598956,"long":-122.400612831116}, ...
Expected: {“id”:”123","location":{"lat":"37.7821120598956","long":"-122. ...
Let’s generate some JSON
{

"id": 123,

"message": "Hello #SFScala",

"location": {

"lat": "37.7821120598956",

"long": "-122.400612831116"

},

"sensitive": false

}
case class RenderableTweet(

id: StatusId,

message: String,

location: Option[RenderableLocation],

sensitive: Boolean)

case class StatusId(

id: String)

extends WrappedValue[String]
case class RenderableLocation(

lat: String,

long: String)
Update Controller to use RenderableTweet
class TweetsController extends Controller {



post("/tweet") { postedTweet: PostedTweet =>

val statusId = StatusId("123")

val status = postedTweet.toDomain(statusId)
// Save status here


val renderableTweet = RenderableTweet.fromDomain(status)

response

.created(renderableTweet)

.location(renderableTweet.id)

}

}

Feature Test Success!
===========================================================================
HTTP POST /tweet
...
===========================================================================
127.0.0.1 - - [24/Apr/2015:22:38:31 +0000] "POST /tweet HTTP/1.1" 201 122 642 "-"
---------------------------------------------------------------------------
[Status] 201 Created
[Header] Content-Type -> application/json; charset=utf-8
[Header] Location -> http://127.0.0.1:60352/tweet/123
[Header] Content-Length -> 122
{
"id" : "123",
"message" : "Hello #SFScala",
"location" : {
"lat" : "37.7821120598956",
"long" : "-122.400612831116"
},
"sensitive" : false
}
Let’s test that ‘message’ is required
"Missing message field" in {

server.httpPost(

path = "/tweet",

postBody = """

{

"location": {

"lat": "37.7821120598956",

"long": "-122.400612831116"

},

"sensitive": false

}""",

andExpect = BadRequest)

}
Test Success
===========================================================================
127.0.0.1 - - [24/Apr/2015:22:57:56 +0000] "POST /tweet HTTP/1.1" 400 42 570 "-"
---------------------------------------------------------------------------
[Status] 400 Bad Request
[Header] Content-Type -> application/json;charset=utf-8
[Header] Content-Length -> 42
{
"errors" : [
"message is a required field"
]
}
Let’s test for invalid values
"Invalid fields" in {

server.httpPost(

path = "/tweet",

postBody = """

{

"message": "",

"location": {

"lat": "9999",

"long": "-122.400612831116"

},

"sensitive": false

}""",

andExpect = BadRequest)

}
Test Failed (expected 400 Bad Request)
===========================================================================
127.0.0.1 - - [25/Apr/2015:01:58:27 +0000] "POST /tweet HTTP/1.1" 201 98 742 "-"
---------------------------------------------------------------------------
[Status] 201 Created
[Header] Content-Type -> application/json; charset=utf-8
[Header] Location -> http://127.0.0.1:49189/tweet/123
[Header] Content-Length -> 98
{
"id" : "123",
"message" : "",
"location" : {
"lat" : "9999.0",
"long" : "-122.400612831116"
},
"sensitive" : false
}
201 Created did not equal 400 Bad Request
Validation Annotations
• CountryCode
• FutureTime
• PastTime
• Max
• Min
• NonEmpty
• OneOf
• Range
• Size
• TimeGranularity
• UUID
• MethodValidation
Let’s add some validation annotations
case class PostedTweet(

@Size(min = 1, max = 140) message: String,

location: Option[Location],

sensitive: Boolean = false) {
case class Location(

@Range(min = -90, max = 90) lat: Double,

@Range(min = -180, max = 180) long: Double)

Test Success
===========================================================================
127.0.0.1 - - [24/Apr/2015:23:01:10 +0000] "POST /tweet HTTP/1.1" 400 106 660 "-"
---------------------------------------------------------------------------
[Status] 400 Bad Request
[Header] Content-Type -> application/json;charset=utf-8
[Header] Content-Length -> 106
{
"errors" : [
"message size [0] is not between 1 and 140",
"location.lat [9999.0] is not between -90 and 90"
]
}
Next let’s write a class to save a tweet
@Singleton

class TweetsService @Inject()(

idService: IdService,

firebase: FirebaseClient) {



def save(postedTweet: PostedTweet): Future[Status] = {

for {

id <- idService.getId()

status = postedTweet.toStatus(id)
path = s"/statuses/${status.id}.json"

_ <- firebase.put(path, status)

} yield status

}

}
Inject TweetsService into Controller
@Singleton

class TweetsController @Inject()(

tweetsService: TweetsService)

extends Controller {



post("/tweet") { postedTweet: PostedTweet =>

tweetsService.save(postedTweet) map { status =>
val renderableTweet = RenderableTweet.fromDomain(status)

response

.created(renderableTweet)

.location(renderableTweet.id)

}

}

}
Update Server
//Before
class TwitterCloneServer extends HttpServer {



override def configureHttp(router: HttpRouter): Unit = {

router

.filter[CommonFilters]

.add(new TweetsController)

}

}
//After
class TwitterCloneServer extends HttpServer {



override def modules = Seq(FirebaseHttpClientModule)



override def configureHttp(router: HttpRouter): Unit = {

router

.filter[CommonFilters]

.add[TweetsController]

}

}
FirebaseHttpClientModule
object FirebaseHttpClientModule extends HttpClientModule {



override val dest = "flag!firebase"



override def retryPolicy = Some(exponentialRetry(

start = 10.millis,

multiplier = 2,

numRetries = 3,

shouldRetry = Http4xxOr5xxResponses))

}

Rerun ‘Post tweet’ Test
"Post tweet" in {

server.httpPost(

path = "/tweet",

postBody = ...,

andExpect = Created,

withLocationHeader = "/tweet/123",

withJsonBody = """

{

"id": "123",

"message": "Hello #SFScala",

"location": {

"lat": "37.7821120598956",

"long": "-122.400612831116"

},

"sensitive": false

}""")

}
Test Fails: “no hosts are available”
===========================================================================
HTTP POST /tweet
{
"message" : "Hello #SFScala",
...
}
===========================================================================
Request("PUT /statuses/374ea2dd-846a-45a5-a296-bcf43e5830c3.json”)
service unavailable com.twitter.finagle.NoBrokersAvailableException:
No hosts are available for flag!firebase
127.0.0.1 - - [25/Apr/2015:02:37:01 +0000] "POST /tweet HTTP/1.1" 503 34 584 "-"
---------------------------------------------------------------------------
[Status] 503 Service Unavailable
[Header] Content-Type -> application/json;charset=utf-8
[Header] Content-Length -> 34
{
"errors" : [
"service unavailable"
]
}
Let’s mock our services
// Before

class TwitterCloneFeatureTest extends Test {

val server = new EmbeddedHttpServer(

twitterServer = new TwitterCloneServer)



"Post tweet" in { ... }
}
// After

class TwitterCloneFeatureTest extends FeatureTest with Mockito {



override val server = new EmbeddedHttpServer(

twitterServer = new TwitterCloneServer {

override val overrideModules = Seq(integrationTestModule)

})



@Bind val firebaseClient = smartMock[FirebaseClient]



@Bind val idService = smartMock[IdService]



“Post tweet" in { ... }
}
Let’s mock our services
class TwitterCloneFeatureTest extends FeatureTest with Mockito {



override val server = new EmbeddedHttpServer(

twitterServer = new TwitterCloneServer {

override val overrideModules = Seq(integrationTestModule)

})



@Bind val firebaseClient = smartMock[FirebaseClient]



@Bind val idService = smartMock[IdService]



“Post tweet" in {
val statusId = StatusId("123")

idService.getId returns Future(statusId)



val putStatus = Status(id = statusId, ...)

firebaseClient.put("/statuses/123.json", putStatus) returns Future.Unit
val result = server.httpPost(

path = "/tweet",
...)

Feature Test Success!
===========================================================================
HTTP POST /tweet
...
===========================================================================
127.0.0.1 - - [24/Apr/2015:22:38:31 +0000] "POST /tweet HTTP/1.1" 201 122 642 "-"
---------------------------------------------------------------------------
[Status] 201 Created
[Header] Content-Type -> application/json; charset=utf-8
[Header] Location -> http://127.0.0.1:60352/tweet/123
[Header] Content-Length -> 122
{
"id" : "123",
"message" : "Hello #SFScala",
"location" : {
"lat" : "37.7821120598956",
"long" : "-122.400612831116"
},
"sensitive" : false
}
Startup Test
class TwitterCloneStartupTest extends Test {



val server = new EmbeddedHttpServer(

stage = PRODUCTION,

twitterServer = new TwitterCloneServer,

clientFlags = Map(

"com.twitter.server.resolverMap" -> "firebase=nil!"))



"server" in {

server.assertHealthy()

}

}
Guice with Startup Tests
• Guice provides significant modularity and testing benefits
• Startup tests mostly mitigate lack of compile time safety
• We’ve found the combination to be a good compromise
Additional v2 Features
• Message body readers and writers
• Declarative request parsing
• Injectable flags
• Mustache Templates
• Multi-Server Tests
• Server Warmup
Message body readers and writers
class StatusMessageBodyWriter extends MessageBodyWriter[Status] {



override def write(status: Status): WriterResponse = {

WriterResponse(

MediaType.JSON_UTF_8,

RenderableTweet.fromDomain(status))

}

}
Declarative request parsing
case class GetTweetRequest(

@RouteParam id: StatusId,

@QueryParam expand: Boolean = false)
case class GetTweetsRequest(

@Range(min = 1, max = 100) @QueryParam count: Int = 50)
@Singleton

class TweetsController extends Controller {


get(“/tweet/:id”) { request: GetTweetRequest =>

...
}
get(“/tweets/?”) { request: GetTweetsRequest =>

...
}

}
Injectable flags
@Singleton

class TweetsService @Inject()(

@Flag("max.count") maxCount: Int,

idService: IdService,

firebase: FirebaseClient) {
...
}
Mustache templates
// user.mustache
id:{{id}}
name:{{name}}

@Mustache("user")

case class UserView(

id: Int,

name: String)

get("/") { request: Request =>

UserView(

123,

"Bob")

}
Server Warmup
class TwitterCloneServer extends HttpServer {

override val resolveFinagleClientsOnStartup = true



override def warmup() {

run[TwitterCloneWarmup]()

}
...

}
Multi-Server tests
class EchoHttpServerFeatureTest extends Test {



val thriftServer = new EmbeddedThriftServer(

twitterServer = new EchoThriftServer)



val httpServer = new EmbeddedHttpServer(

twitterServer = new EchoHttpServer,

clientFlags = Map(

resolverMap(“flag!echo-service“ -> thriftServer.thriftExternalPort)))



"EchoHttpServer" should {

"Echo msg" in {

httpServer.httpGet(

path = "/echo?msg=Bob",

andExpect = Ok,

withBody = "Bob")

}

}

}

Finatra Contributors
• Steve Cosenza
• Christopher Coco
• Jason Carey
• Eugene Ma
Questions?
class TwitterCloneServer extends HttpServer {

override val resolveFinagleClientsOnStartup = true

override val modules = Seq(FirebaseHttpClientModule)

override def warmup { run[TwitterCloneWarmup]() }
override def configureHttp(router: HttpRouter): Unit = {

router

.register[StatusMessageBodyWriter]

.filter[CommonFilters]

.add[TweetsController]

}

}
@Singleton

class TweetsController @Inject()(

tweetsService: TweetsService)

extends Controller {

post("/tweet") { postedTweet: PostedTweet =>

tweetsService.save(postedTweet) map { status =>

response

.created(status)

.location(status.id)

}

}

}

Mais conteúdo relacionado

Mais procurados

Autoscaling with hashi_corp_nomad
Autoscaling with hashi_corp_nomadAutoscaling with hashi_corp_nomad
Autoscaling with hashi_corp_nomadBram Vogelaar
 
Introducing Middy, Node.js middleware engine for AWS Lambda (FrontConf Munich...
Introducing Middy, Node.js middleware engine for AWS Lambda (FrontConf Munich...Introducing Middy, Node.js middleware engine for AWS Lambda (FrontConf Munich...
Introducing Middy, Node.js middleware engine for AWS Lambda (FrontConf Munich...Luciano Mammino
 
VCL template abstraction model and automated deployments to Fastly
VCL template abstraction model and automated deployments to FastlyVCL template abstraction model and automated deployments to Fastly
VCL template abstraction model and automated deployments to FastlyFastly
 
Distributed Eventing in OSGi
Distributed Eventing in OSGiDistributed Eventing in OSGi
Distributed Eventing in OSGiCarsten Ziegeler
 
Composable and streamable Play apps
Composable and streamable Play appsComposable and streamable Play apps
Composable and streamable Play appsYevgeniy Brikman
 
Java Play RESTful ebean
Java Play RESTful ebeanJava Play RESTful ebean
Java Play RESTful ebeanFaren faren
 
Java Play Restful JPA
Java Play Restful JPAJava Play Restful JPA
Java Play Restful JPAFaren faren
 
Serverless, The Middy Way - Workshop
Serverless, The Middy Way - WorkshopServerless, The Middy Way - Workshop
Serverless, The Middy Way - WorkshopLuciano Mammino
 
Middy.js - A powerful Node.js middleware framework for your lambdas​
Middy.js - A powerful Node.js middleware framework for your lambdas​ Middy.js - A powerful Node.js middleware framework for your lambdas​
Middy.js - A powerful Node.js middleware framework for your lambdas​ Luciano Mammino
 
Observability with Consul Connect
Observability with Consul ConnectObservability with Consul Connect
Observability with Consul ConnectBram Vogelaar
 
Advanced VCL: how to use restart
Advanced VCL: how to use restartAdvanced VCL: how to use restart
Advanced VCL: how to use restartFastly
 
Integrating icinga2 and the HashiCorp suite
Integrating icinga2 and the HashiCorp suiteIntegrating icinga2 and the HashiCorp suite
Integrating icinga2 and the HashiCorp suiteBram Vogelaar
 
Get Real: Adventures in realtime web apps
Get Real: Adventures in realtime web appsGet Real: Adventures in realtime web apps
Get Real: Adventures in realtime web appsdaviddemello
 
Static Typing in Vault
Static Typing in VaultStatic Typing in Vault
Static Typing in VaultGlynnForrest
 
{{more}} Kibana4
{{more}} Kibana4{{more}} Kibana4
{{more}} Kibana4琛琳 饶
 
Java9 Beyond Modularity - Java 9 más allá de la modularidad
Java9 Beyond Modularity - Java 9 más allá de la modularidadJava9 Beyond Modularity - Java 9 más allá de la modularidad
Java9 Beyond Modularity - Java 9 más allá de la modularidadDavid Gómez García
 
8 Minutes On Rack
8 Minutes On Rack8 Minutes On Rack
8 Minutes On Rackdanwrong
 
Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015Masahiro Nagano
 

Mais procurados (20)

Autoscaling with hashi_corp_nomad
Autoscaling with hashi_corp_nomadAutoscaling with hashi_corp_nomad
Autoscaling with hashi_corp_nomad
 
Introducing Middy, Node.js middleware engine for AWS Lambda (FrontConf Munich...
Introducing Middy, Node.js middleware engine for AWS Lambda (FrontConf Munich...Introducing Middy, Node.js middleware engine for AWS Lambda (FrontConf Munich...
Introducing Middy, Node.js middleware engine for AWS Lambda (FrontConf Munich...
 
VCL template abstraction model and automated deployments to Fastly
VCL template abstraction model and automated deployments to FastlyVCL template abstraction model and automated deployments to Fastly
VCL template abstraction model and automated deployments to Fastly
 
Distributed Eventing in OSGi
Distributed Eventing in OSGiDistributed Eventing in OSGi
Distributed Eventing in OSGi
 
Composable and streamable Play apps
Composable and streamable Play appsComposable and streamable Play apps
Composable and streamable Play apps
 
Java Play RESTful ebean
Java Play RESTful ebeanJava Play RESTful ebean
Java Play RESTful ebean
 
Java Play Restful JPA
Java Play Restful JPAJava Play Restful JPA
Java Play Restful JPA
 
Serverless, The Middy Way - Workshop
Serverless, The Middy Way - WorkshopServerless, The Middy Way - Workshop
Serverless, The Middy Way - Workshop
 
Middy.js - A powerful Node.js middleware framework for your lambdas​
Middy.js - A powerful Node.js middleware framework for your lambdas​ Middy.js - A powerful Node.js middleware framework for your lambdas​
Middy.js - A powerful Node.js middleware framework for your lambdas​
 
Observability with Consul Connect
Observability with Consul ConnectObservability with Consul Connect
Observability with Consul Connect
 
Advanced VCL: how to use restart
Advanced VCL: how to use restartAdvanced VCL: how to use restart
Advanced VCL: how to use restart
 
Integrating icinga2 and the HashiCorp suite
Integrating icinga2 and the HashiCorp suiteIntegrating icinga2 and the HashiCorp suite
Integrating icinga2 and the HashiCorp suite
 
Get Real: Adventures in realtime web apps
Get Real: Adventures in realtime web appsGet Real: Adventures in realtime web apps
Get Real: Adventures in realtime web apps
 
Pycon - Python for ethical hackers
Pycon - Python for ethical hackers Pycon - Python for ethical hackers
Pycon - Python for ethical hackers
 
Static Typing in Vault
Static Typing in VaultStatic Typing in Vault
Static Typing in Vault
 
{{more}} Kibana4
{{more}} Kibana4{{more}} Kibana4
{{more}} Kibana4
 
Rack Middleware
Rack MiddlewareRack Middleware
Rack Middleware
 
Java9 Beyond Modularity - Java 9 más allá de la modularidad
Java9 Beyond Modularity - Java 9 más allá de la modularidadJava9 Beyond Modularity - Java 9 más allá de la modularidad
Java9 Beyond Modularity - Java 9 más allá de la modularidad
 
8 Minutes On Rack
8 Minutes On Rack8 Minutes On Rack
8 Minutes On Rack
 
Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015
 

Destaque

Async Microservices with Twitter's Finagle
Async Microservices with Twitter's FinagleAsync Microservices with Twitter's Finagle
Async Microservices with Twitter's FinagleVladimir Kostyukov
 
Scalaの現状と課題
Scalaの現状と課題Scalaの現状と課題
Scalaの現状と課題Kota Mizushima
 
08.11.14 Impact Call Slides
08.11.14 Impact Call Slides08.11.14 Impact Call Slides
08.11.14 Impact Call SlidesGuideStar
 
Finch.io - Purely Functional REST API with Finagle
Finch.io - Purely Functional REST API with FinagleFinch.io - Purely Functional REST API with Finagle
Finch.io - Purely Functional REST API with FinagleVladimir Kostyukov
 
Understanding Akka Streams, Back Pressure, and Asynchronous Architectures
Understanding Akka Streams, Back Pressure, and Asynchronous ArchitecturesUnderstanding Akka Streams, Back Pressure, and Asynchronous Architectures
Understanding Akka Streams, Back Pressure, and Asynchronous ArchitecturesLightbend
 

Destaque (7)

Async Microservices with Twitter's Finagle
Async Microservices with Twitter's FinagleAsync Microservices with Twitter's Finagle
Async Microservices with Twitter's Finagle
 
Akka programação concorrente
Akka   programação concorrenteAkka   programação concorrente
Akka programação concorrente
 
Scalaの現状と課題
Scalaの現状と課題Scalaの現状と課題
Scalaの現状と課題
 
08.11.14 Impact Call Slides
08.11.14 Impact Call Slides08.11.14 Impact Call Slides
08.11.14 Impact Call Slides
 
Finch + Finagle OAuth2
Finch + Finagle OAuth2Finch + Finagle OAuth2
Finch + Finagle OAuth2
 
Finch.io - Purely Functional REST API with Finagle
Finch.io - Purely Functional REST API with FinagleFinch.io - Purely Functional REST API with Finagle
Finch.io - Purely Functional REST API with Finagle
 
Understanding Akka Streams, Back Pressure, and Asynchronous Architectures
Understanding Akka Streams, Back Pressure, and Asynchronous ArchitecturesUnderstanding Akka Streams, Back Pressure, and Asynchronous Architectures
Understanding Akka Streams, Back Pressure, and Asynchronous Architectures
 

Semelhante a Finatra v2

Taming WebSocket with Scarlet
Taming WebSocket with ScarletTaming WebSocket with Scarlet
Taming WebSocket with ScarletZhixuan Lai
 
Workshop KrakYourNet2016 - Web applications hacking Ruby on Rails example
Workshop KrakYourNet2016 - Web applications hacking Ruby on Rails example Workshop KrakYourNet2016 - Web applications hacking Ruby on Rails example
Workshop KrakYourNet2016 - Web applications hacking Ruby on Rails example Anna Klepacka
 
Streaming twitter data using kafka
Streaming twitter data using kafkaStreaming twitter data using kafka
Streaming twitter data using kafkaKiran Krishna
 
RoR Workshop - Web applications hacking - Ruby on Rails example
RoR Workshop - Web applications hacking - Ruby on Rails exampleRoR Workshop - Web applications hacking - Ruby on Rails example
RoR Workshop - Web applications hacking - Ruby on Rails exampleRailwaymen
 
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 202010 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020Matt Raible
 
Efficient HTTP Apis
Efficient HTTP ApisEfficient HTTP Apis
Efficient HTTP ApisAdrian Cole
 
twMVC#46 一探 C# 11 與 .NET 7 的神奇
twMVC#46 一探 C# 11 與 .NET 7 的神奇twMVC#46 一探 C# 11 與 .NET 7 的神奇
twMVC#46 一探 C# 11 與 .NET 7 的神奇twMVC
 
Java Servlets.pdf
Java Servlets.pdfJava Servlets.pdf
Java Servlets.pdfArumugam90
 
SharePoint Fest Chicago 2015 - Anatomy of configuring provider hosted add-in...
SharePoint Fest Chicago 2015  - Anatomy of configuring provider hosted add-in...SharePoint Fest Chicago 2015  - Anatomy of configuring provider hosted add-in...
SharePoint Fest Chicago 2015 - Anatomy of configuring provider hosted add-in...Nik Patel
 
OpenStack API's and WSGI
OpenStack API's and WSGIOpenStack API's and WSGI
OpenStack API's and WSGIMike Pittaro
 
GitBucket: The perfect Github clone by Scala
GitBucket: The perfect Github clone by ScalaGitBucket: The perfect Github clone by Scala
GitBucket: The perfect Github clone by Scalatakezoe
 
Solving anything in VCL
Solving anything in VCLSolving anything in VCL
Solving anything in VCLFastly
 
Spring MVC to iOS and the REST
Spring MVC to iOS and the RESTSpring MVC to iOS and the REST
Spring MVC to iOS and the RESTRoy Clarkson
 
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade ServerlessKatyShimizu
 
[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade Serverless[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade ServerlessKatyShimizu
 
Securing your Pulsar Cluster with Vault_Chris Kellogg
Securing your Pulsar Cluster with Vault_Chris KelloggSecuring your Pulsar Cluster with Vault_Chris Kellogg
Securing your Pulsar Cluster with Vault_Chris KelloggStreamNative
 
Construindo APIs Usando Rails
Construindo APIs Usando RailsConstruindo APIs Usando Rails
Construindo APIs Usando RailsFernando Kakimoto
 
Sharding and Load Balancing in Scala - Twitter's Finagle
Sharding and Load Balancing in Scala - Twitter's FinagleSharding and Load Balancing in Scala - Twitter's Finagle
Sharding and Load Balancing in Scala - Twitter's FinagleGeoff Ballinger
 

Semelhante a Finatra v2 (20)

Taming WebSocket with Scarlet
Taming WebSocket with ScarletTaming WebSocket with Scarlet
Taming WebSocket with Scarlet
 
Workshop KrakYourNet2016 - Web applications hacking Ruby on Rails example
Workshop KrakYourNet2016 - Web applications hacking Ruby on Rails example Workshop KrakYourNet2016 - Web applications hacking Ruby on Rails example
Workshop KrakYourNet2016 - Web applications hacking Ruby on Rails example
 
Streaming twitter data using kafka
Streaming twitter data using kafkaStreaming twitter data using kafka
Streaming twitter data using kafka
 
RoR Workshop - Web applications hacking - Ruby on Rails example
RoR Workshop - Web applications hacking - Ruby on Rails exampleRoR Workshop - Web applications hacking - Ruby on Rails example
RoR Workshop - Web applications hacking - Ruby on Rails example
 
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 202010 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020
 
BEST REST in OpenStack
BEST REST in OpenStackBEST REST in OpenStack
BEST REST in OpenStack
 
Efficient HTTP Apis
Efficient HTTP ApisEfficient HTTP Apis
Efficient HTTP Apis
 
twMVC#46 一探 C# 11 與 .NET 7 的神奇
twMVC#46 一探 C# 11 與 .NET 7 的神奇twMVC#46 一探 C# 11 與 .NET 7 的神奇
twMVC#46 一探 C# 11 與 .NET 7 的神奇
 
Java Servlets.pdf
Java Servlets.pdfJava Servlets.pdf
Java Servlets.pdf
 
SharePoint Fest Chicago 2015 - Anatomy of configuring provider hosted add-in...
SharePoint Fest Chicago 2015  - Anatomy of configuring provider hosted add-in...SharePoint Fest Chicago 2015  - Anatomy of configuring provider hosted add-in...
SharePoint Fest Chicago 2015 - Anatomy of configuring provider hosted add-in...
 
OpenStack API's and WSGI
OpenStack API's and WSGIOpenStack API's and WSGI
OpenStack API's and WSGI
 
GitBucket: The perfect Github clone by Scala
GitBucket: The perfect Github clone by ScalaGitBucket: The perfect Github clone by Scala
GitBucket: The perfect Github clone by Scala
 
WCF - In a Week
WCF - In a WeekWCF - In a Week
WCF - In a Week
 
Solving anything in VCL
Solving anything in VCLSolving anything in VCL
Solving anything in VCL
 
Spring MVC to iOS and the REST
Spring MVC to iOS and the RESTSpring MVC to iOS and the REST
Spring MVC to iOS and the REST
 
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
 
[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade Serverless[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade Serverless
 
Securing your Pulsar Cluster with Vault_Chris Kellogg
Securing your Pulsar Cluster with Vault_Chris KelloggSecuring your Pulsar Cluster with Vault_Chris Kellogg
Securing your Pulsar Cluster with Vault_Chris Kellogg
 
Construindo APIs Usando Rails
Construindo APIs Usando RailsConstruindo APIs Usando Rails
Construindo APIs Usando Rails
 
Sharding and Load Balancing in Scala - Twitter's Finagle
Sharding and Load Balancing in Scala - Twitter's FinagleSharding and Load Balancing in Scala - Twitter's Finagle
Sharding and Load Balancing in Scala - Twitter's Finagle
 

Último

Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作qr0udbr0
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Velvetech LLC
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样umasea
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfStefano Stabellini
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commercemanigoyal112
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
PREDICTING RIVER WATER QUALITY ppt presentation
PREDICTING  RIVER  WATER QUALITY  ppt presentationPREDICTING  RIVER  WATER QUALITY  ppt presentation
PREDICTING RIVER WATER QUALITY ppt presentationvaddepallysandeep122
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odishasmiwainfosol
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Cizo Technology Services
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Natan Silnitsky
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationBradBedford3
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio, Inc.
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...OnePlan Solutions
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprisepreethippts
 
How to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdfHow to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdfLivetecs LLC
 

Último (20)

Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdf
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commerce
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
PREDICTING RIVER WATER QUALITY ppt presentation
PREDICTING  RIVER  WATER QUALITY  ppt presentationPREDICTING  RIVER  WATER QUALITY  ppt presentation
PREDICTING RIVER WATER QUALITY ppt presentation
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion Application
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprise
 
How to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdfHow to Track Employee Performance A Comprehensive Guide.pdf
How to Track Employee Performance A Comprehensive Guide.pdf
 

Finatra v2

  • 1. Finatra v2 Fast, testable Scala services Steve Cosenza @scosenza Finatra & Data API Tech Lead @twitter
  • 2. What is Finatra? • Finatra is a framework for easily building API services on top of Twitter’s Scala stack (twitter-server, finagle, twitter-util) • Currently supports building HTTP services • Plan to support building Thrift services
  • 3. Project History • Feb 2012: Finatra project started on Github by two Twitter engineers (Julio Capote and Chris Burnett) • June 2014: Christopher Coco and Steve Cosenza on the Data API team release Finatra v2.0.0-SNAPSHOT internally at Twitter • Jan 2015: Finatra v1.6 released for Scala 2.9 and 2.10 • April 2015: Finatra v2.0.0M1 publicly released for Scala 2.10 and 2.11
  • 4. Highlights • Production use as Twitter’s HTTP framework • ~50 times faster than v1.6 in several benchmarks • Powerful feature and integration test support • JSR-330 Dependency Injection using Google Guice • Jackson based JSON parsing supporting required fields, default values, and custom validations • Logback MDC integration for contextual logging across Futures • Guice request scope integration with Futures
  • 5. Twitter Inject Libraries • inject-core • inject-app • inject-server • inject-modules • inject-thrift-client • inject-request-scope
  • 6. Finatra Libraries • finatra-http • finatra-jackson • finatra-logback • finatra-httpclient • finatra-utils
  • 7. Tutorial Let’s create a simple HTTP API for Tweets :-)
  • 8. Create a FeatureTest class TwitterCloneFeatureTest extends Test { 
 val server = new EmbeddedHttpServer(
 twitterServer = new HttpServer {})
 
 "Post tweet" in {
 server.httpPost(
 path = "/tweet",
 postBody = """
 {
 "message": "Hello #SFScala",
 "location": {
 "lat": "37.7821120598956",
 "long": "-122.400612831116"
 },
 "sensitive": false
 }”"", andExpect = Created, withLocationHeader = "/tweet/123")
 }
 }
  • 9. And let’s assert the response body "Post tweet" in {
 server.httpPost(
 path = "/tweet",
 postBody = """
 {
 "message": "Hello #SFScala",
 "location": {...} "sensitive": false
 }”"",
 andExpect = Created,
 withLocationHeader = "/tweet/123",
 withJsonBody = """
 {
 "id": "123",
 "message": "Hello #SFScala",
 "location": {
 "lat": "37.7821120598956",
 "long": "-122.400612831116"
 },
 "sensitive": false
 }""")
 }
  • 10. Test Failure: Route not found =========================================================================== HTTP POST /tweet [Header] Content-Length -> 204 [Header] Content-Type -> application/json;charset=utf-8 [Header] Host -> 127.0.0.1:60102 { "message" : "Hello #SFScala", "location" : { "lat" : "37.7821120598956", "long" : "-122.400612831116" }, "sensitive" : false } =========================================================================== RoutingService Request("POST /tweet", from /127.0.0.1:60103) not found in registered routes: --------------------------------------------------------------------------- [Status] 404 Not Found [Header] Content-Length -> 0 *EmptyBody*
  • 11. Create a Controller package finatra.quickstart.controllers
 
 import com.twitter.finagle.http.Request
 import com.twitter.finatra.http.Controller
 
 class TweetsController extends Controller {
 
 post("/tweet") { request: Request =>
 response.created("hello")
 }
 }
  • 12. Create a Server import com.twitter.finatra.HttpServer
 import com.twitter.finatra.filters.CommonFilters
 import com.twitter.finatra.routing.HttpRouter
 import finatra.quickstart.controllers.TweetsControllerV1
 
 class TwitterCloneServer extends HttpServer {
 
 override def configureHttp(router: HttpRouter): Unit = {
 router
 .filter[CommonFilters]
 .add(new TweetsController)
 }
 }
  • 13. Add Server to FeatureTest class TwitterCloneFeatureTest extends Test { 
 val server = new EmbeddedHttpServer(
 twitterServer = new TwitterCloneServer)
 
 ... }
  • 14. Test Failure: Unexpected response body =========================================================================== HTTP POST /tweet [Header] Content-Length -> 204 [Header] Content-Type -> application/json;charset=utf-8 [Header] Host -> 127.0.0.1:60118 ... =========================================================================== 127.0.0.1 - - [24/Apr/2015:21:36:25 +0000] "POST /tweet HTTP/1.1" 201 5 9 "-" --------------------------------------------------------------------------- [Status] 201 Created [Header] Content-Length -> 5 hello Unrecognized token 'hello': was expecting ('true', 'false' or 'null') at [Source: hello; line: 1, column: 11] JSON DIFF FAILED! * Received: hello Expected: {“id":"123","location":{"lat":"37.7821120598956","long":"-122.4006 ...
  • 15. Let’s parse some JSON {
 "message": "Hello #SFScala",
 "location": {
 "lat": "37.7821120598956",
 "long": "-122.400612831116"
 },
 "sensitive": false
 } case class PostedTweet(
 message: String,
 location: Option[Location],
 sensitive: Boolean = false) {
 
 def toStatus(id: StatusId) = { ... }
 } case class Location(
 lat: Double,
 long: Double)

  • 16. Update Controller to use PostedTweet class TweetsController extends Controller {
 
 post("/tweet") { postedTweet: PostedTweet =>
 response.created(postedTweet)
 }
 }

  • 17. Test failure: ’id’ field in body missing [Status] 201 Created [Header] Content-Type -> application/json; charset=utf-8 [Header] Content-Length -> 107 { "message" : "Hello #SFScala", "location" : { "lat" : 37.7821120598956, "long" : -122.400612831116 }, "sensitive" : false } JSON DIFF FAILED! * Received: {“location”:{"lat":37.7821120598956,"long":-122.400612831116}, ... Expected: {“id”:”123","location":{"lat":"37.7821120598956","long":"-122. ...
  • 18. Let’s generate some JSON {
 "id": 123,
 "message": "Hello #SFScala",
 "location": {
 "lat": "37.7821120598956",
 "long": "-122.400612831116"
 },
 "sensitive": false
 } case class RenderableTweet(
 id: StatusId,
 message: String,
 location: Option[RenderableLocation],
 sensitive: Boolean)
 case class StatusId(
 id: String)
 extends WrappedValue[String] case class RenderableLocation(
 lat: String,
 long: String)
  • 19. Update Controller to use RenderableTweet class TweetsController extends Controller {
 
 post("/tweet") { postedTweet: PostedTweet =>
 val statusId = StatusId("123")
 val status = postedTweet.toDomain(statusId) // Save status here 
 val renderableTweet = RenderableTweet.fromDomain(status)
 response
 .created(renderableTweet)
 .location(renderableTweet.id)
 }
 }

  • 20. Feature Test Success! =========================================================================== HTTP POST /tweet ... =========================================================================== 127.0.0.1 - - [24/Apr/2015:22:38:31 +0000] "POST /tweet HTTP/1.1" 201 122 642 "-" --------------------------------------------------------------------------- [Status] 201 Created [Header] Content-Type -> application/json; charset=utf-8 [Header] Location -> http://127.0.0.1:60352/tweet/123 [Header] Content-Length -> 122 { "id" : "123", "message" : "Hello #SFScala", "location" : { "lat" : "37.7821120598956", "long" : "-122.400612831116" }, "sensitive" : false }
  • 21. Let’s test that ‘message’ is required "Missing message field" in {
 server.httpPost(
 path = "/tweet",
 postBody = """
 {
 "location": {
 "lat": "37.7821120598956",
 "long": "-122.400612831116"
 },
 "sensitive": false
 }""",
 andExpect = BadRequest)
 }
  • 22. Test Success =========================================================================== 127.0.0.1 - - [24/Apr/2015:22:57:56 +0000] "POST /tweet HTTP/1.1" 400 42 570 "-" --------------------------------------------------------------------------- [Status] 400 Bad Request [Header] Content-Type -> application/json;charset=utf-8 [Header] Content-Length -> 42 { "errors" : [ "message is a required field" ] }
  • 23. Let’s test for invalid values "Invalid fields" in {
 server.httpPost(
 path = "/tweet",
 postBody = """
 {
 "message": "",
 "location": {
 "lat": "9999",
 "long": "-122.400612831116"
 },
 "sensitive": false
 }""",
 andExpect = BadRequest)
 }
  • 24. Test Failed (expected 400 Bad Request) =========================================================================== 127.0.0.1 - - [25/Apr/2015:01:58:27 +0000] "POST /tweet HTTP/1.1" 201 98 742 "-" --------------------------------------------------------------------------- [Status] 201 Created [Header] Content-Type -> application/json; charset=utf-8 [Header] Location -> http://127.0.0.1:49189/tweet/123 [Header] Content-Length -> 98 { "id" : "123", "message" : "", "location" : { "lat" : "9999.0", "long" : "-122.400612831116" }, "sensitive" : false } 201 Created did not equal 400 Bad Request
  • 25. Validation Annotations • CountryCode • FutureTime • PastTime • Max • Min • NonEmpty • OneOf • Range • Size • TimeGranularity • UUID • MethodValidation
  • 26. Let’s add some validation annotations case class PostedTweet(
 @Size(min = 1, max = 140) message: String,
 location: Option[Location],
 sensitive: Boolean = false) { case class Location(
 @Range(min = -90, max = 90) lat: Double,
 @Range(min = -180, max = 180) long: Double)

  • 27. Test Success =========================================================================== 127.0.0.1 - - [24/Apr/2015:23:01:10 +0000] "POST /tweet HTTP/1.1" 400 106 660 "-" --------------------------------------------------------------------------- [Status] 400 Bad Request [Header] Content-Type -> application/json;charset=utf-8 [Header] Content-Length -> 106 { "errors" : [ "message size [0] is not between 1 and 140", "location.lat [9999.0] is not between -90 and 90" ] }
  • 28. Next let’s write a class to save a tweet @Singleton
 class TweetsService @Inject()(
 idService: IdService,
 firebase: FirebaseClient) {
 
 def save(postedTweet: PostedTweet): Future[Status] = {
 for {
 id <- idService.getId()
 status = postedTweet.toStatus(id) path = s"/statuses/${status.id}.json"
 _ <- firebase.put(path, status)
 } yield status
 }
 }
  • 29. Inject TweetsService into Controller @Singleton
 class TweetsController @Inject()(
 tweetsService: TweetsService)
 extends Controller {
 
 post("/tweet") { postedTweet: PostedTweet =>
 tweetsService.save(postedTweet) map { status => val renderableTweet = RenderableTweet.fromDomain(status)
 response
 .created(renderableTweet)
 .location(renderableTweet.id)
 }
 }
 }
  • 30. Update Server //Before class TwitterCloneServer extends HttpServer {
 
 override def configureHttp(router: HttpRouter): Unit = {
 router
 .filter[CommonFilters]
 .add(new TweetsController)
 }
 } //After class TwitterCloneServer extends HttpServer {
 
 override def modules = Seq(FirebaseHttpClientModule)
 
 override def configureHttp(router: HttpRouter): Unit = {
 router
 .filter[CommonFilters]
 .add[TweetsController]
 }
 }
  • 31. FirebaseHttpClientModule object FirebaseHttpClientModule extends HttpClientModule {
 
 override val dest = "flag!firebase"
 
 override def retryPolicy = Some(exponentialRetry(
 start = 10.millis,
 multiplier = 2,
 numRetries = 3,
 shouldRetry = Http4xxOr5xxResponses))
 }

  • 32. Rerun ‘Post tweet’ Test "Post tweet" in {
 server.httpPost(
 path = "/tweet",
 postBody = ...,
 andExpect = Created,
 withLocationHeader = "/tweet/123",
 withJsonBody = """
 {
 "id": "123",
 "message": "Hello #SFScala",
 "location": {
 "lat": "37.7821120598956",
 "long": "-122.400612831116"
 },
 "sensitive": false
 }""")
 }
  • 33. Test Fails: “no hosts are available” =========================================================================== HTTP POST /tweet { "message" : "Hello #SFScala", ... } =========================================================================== Request("PUT /statuses/374ea2dd-846a-45a5-a296-bcf43e5830c3.json”) service unavailable com.twitter.finagle.NoBrokersAvailableException: No hosts are available for flag!firebase 127.0.0.1 - - [25/Apr/2015:02:37:01 +0000] "POST /tweet HTTP/1.1" 503 34 584 "-" --------------------------------------------------------------------------- [Status] 503 Service Unavailable [Header] Content-Type -> application/json;charset=utf-8 [Header] Content-Length -> 34 { "errors" : [ "service unavailable" ] }
  • 34. Let’s mock our services // Before
 class TwitterCloneFeatureTest extends Test {
 val server = new EmbeddedHttpServer(
 twitterServer = new TwitterCloneServer)
 
 "Post tweet" in { ... } } // After
 class TwitterCloneFeatureTest extends FeatureTest with Mockito {
 
 override val server = new EmbeddedHttpServer(
 twitterServer = new TwitterCloneServer {
 override val overrideModules = Seq(integrationTestModule)
 })
 
 @Bind val firebaseClient = smartMock[FirebaseClient]
 
 @Bind val idService = smartMock[IdService]
 
 “Post tweet" in { ... } }
  • 35. Let’s mock our services class TwitterCloneFeatureTest extends FeatureTest with Mockito {
 
 override val server = new EmbeddedHttpServer(
 twitterServer = new TwitterCloneServer {
 override val overrideModules = Seq(integrationTestModule)
 })
 
 @Bind val firebaseClient = smartMock[FirebaseClient]
 
 @Bind val idService = smartMock[IdService]
 
 “Post tweet" in { val statusId = StatusId("123")
 idService.getId returns Future(statusId)
 
 val putStatus = Status(id = statusId, ...)
 firebaseClient.put("/statuses/123.json", putStatus) returns Future.Unit val result = server.httpPost(
 path = "/tweet", ...)

  • 36. Feature Test Success! =========================================================================== HTTP POST /tweet ... =========================================================================== 127.0.0.1 - - [24/Apr/2015:22:38:31 +0000] "POST /tweet HTTP/1.1" 201 122 642 "-" --------------------------------------------------------------------------- [Status] 201 Created [Header] Content-Type -> application/json; charset=utf-8 [Header] Location -> http://127.0.0.1:60352/tweet/123 [Header] Content-Length -> 122 { "id" : "123", "message" : "Hello #SFScala", "location" : { "lat" : "37.7821120598956", "long" : "-122.400612831116" }, "sensitive" : false }
  • 37. Startup Test class TwitterCloneStartupTest extends Test {
 
 val server = new EmbeddedHttpServer(
 stage = PRODUCTION,
 twitterServer = new TwitterCloneServer,
 clientFlags = Map(
 "com.twitter.server.resolverMap" -> "firebase=nil!"))
 
 "server" in {
 server.assertHealthy()
 }
 }
  • 38. Guice with Startup Tests • Guice provides significant modularity and testing benefits • Startup tests mostly mitigate lack of compile time safety • We’ve found the combination to be a good compromise
  • 39. Additional v2 Features • Message body readers and writers • Declarative request parsing • Injectable flags • Mustache Templates • Multi-Server Tests • Server Warmup
  • 40. Message body readers and writers class StatusMessageBodyWriter extends MessageBodyWriter[Status] {
 
 override def write(status: Status): WriterResponse = {
 WriterResponse(
 MediaType.JSON_UTF_8,
 RenderableTweet.fromDomain(status))
 }
 }
  • 41. Declarative request parsing case class GetTweetRequest(
 @RouteParam id: StatusId,
 @QueryParam expand: Boolean = false) case class GetTweetsRequest(
 @Range(min = 1, max = 100) @QueryParam count: Int = 50) @Singleton
 class TweetsController extends Controller { 
 get(“/tweet/:id”) { request: GetTweetRequest =>
 ... } get(“/tweets/?”) { request: GetTweetsRequest =>
 ... }
 }
  • 42. Injectable flags @Singleton
 class TweetsService @Inject()(
 @Flag("max.count") maxCount: Int,
 idService: IdService,
 firebase: FirebaseClient) { ... }
  • 43. Mustache templates // user.mustache id:{{id}} name:{{name}}
 @Mustache("user")
 case class UserView(
 id: Int,
 name: String)
 get("/") { request: Request =>
 UserView(
 123,
 "Bob")
 }
  • 44. Server Warmup class TwitterCloneServer extends HttpServer {
 override val resolveFinagleClientsOnStartup = true
 
 override def warmup() {
 run[TwitterCloneWarmup]()
 } ...
 }
  • 45. Multi-Server tests class EchoHttpServerFeatureTest extends Test {
 
 val thriftServer = new EmbeddedThriftServer(
 twitterServer = new EchoThriftServer)
 
 val httpServer = new EmbeddedHttpServer(
 twitterServer = new EchoHttpServer,
 clientFlags = Map(
 resolverMap(“flag!echo-service“ -> thriftServer.thriftExternalPort)))
 
 "EchoHttpServer" should {
 "Echo msg" in {
 httpServer.httpGet(
 path = "/echo?msg=Bob",
 andExpect = Ok,
 withBody = "Bob")
 }
 }
 }

  • 46. Finatra Contributors • Steve Cosenza • Christopher Coco • Jason Carey • Eugene Ma
  • 47. Questions? class TwitterCloneServer extends HttpServer {
 override val resolveFinagleClientsOnStartup = true
 override val modules = Seq(FirebaseHttpClientModule)
 override def warmup { run[TwitterCloneWarmup]() } override def configureHttp(router: HttpRouter): Unit = {
 router
 .register[StatusMessageBodyWriter]
 .filter[CommonFilters]
 .add[TweetsController]
 }
 } @Singleton
 class TweetsController @Inject()(
 tweetsService: TweetsService)
 extends Controller {
 post("/tweet") { postedTweet: PostedTweet =>
 tweetsService.save(postedTweet) map { status =>
 response
 .created(status)
 .location(status.id)
 }
 }
 }