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] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade Serverless[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade ServerlessKatyShimizu
 
[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
 
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] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade Serverless[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] 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] Functions 2.0: 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

5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)OPEN KNOWLEDGE GmbH
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantAxelRicardoTrocheRiq
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AIABDERRAOUF MEHENNI
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
Test Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendTest Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendArshad QA
 

Último (20)

5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service Consultant
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
Test Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendTest Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and Backend
 

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)
 }
 }
 }