SCALA
does the catwalk

Ian Forsey
@theon
github.com/theon

Ariel Kogan
@arikogan
github.com/arikogan
AGENDA
•

Who are we? What are we building?

•

Architecture

•

Lessons learnt
–

Design evolution

–

Flow: Embrace the pipelines

–

Stress is good

–

Optimise
WHO ARE WE?
WHAT ARE WE BUILDING?
ARCHITECTURE
DOMAINS OF KNOWLEDGE

WISHLIST

PRODUCT

ALERT
SERVICES

WISHLIST
PRESENTATIO
N

WISHLIST
AGGREGATIO
N

WISHLIST

PRODUCT

ALERT

WISHLIST
DB
TECHNOLOGY

SBT

REST
DESIGN EVOLUTION
APPLICATION ARCHITECTURE

REST ROUTING

APPLICATION CORE

DTO

REST CLIENT
Should I use Actor Tells or Actor Asks?

!

?
ACTOR REFRESHER
Tells:
wishlistClient ! GetItemsForWishlist(123)

def receive = {
case items: Items => ...
}

Asks:
implicit val t = Timeout(5 seconds)
val f = wishlistClient ? GetItemsForWishlist(123)
f.map(items => {
...
})
FIRST DESIGNS: ASKS
App Core

Routing

Clients

ask

ask

ROUTING

WISHLIS
T

ask

GET WISHLIST
ITEMS

PRODUCT

ask

ALERT
THREE PROBLEMS
WITH THIS DESIGN
PROBLEM 1
Timeout errors not helpful:
akka.pattern.AskTimeoutException: Timed out
at akka.pattern.PromiseActorRef$$anonfun$1.apply$mcV$sp(AskSupport.scala:312)
at akka.actor.DefaultScheduler$$anon$8.run(Scheduler.scala:191)
at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:137)
at akka.dispatch.ForkJoinExecutorConfigurator$MailboxExecutionTask.exec(…)
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:262)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(…)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1478)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(…)
PROBLEM 1: SHORT-TERM FIX
Better timeout failures:
f recoverWith {
case t: TimeoutException =>
Future.failed(
new TimeoutException(”Alerts API Timeout”)
)
}
PROBLEM 2
Routing

App Core

Clients
5 sec

5
sec

WISHLIS
T

5 sec

ROUTING

GET WISHLIST
ITEMS

PRODUCT

5
sec

5 sec

ALERT
PROBLEM 2: SHORT-TERM FIX
Be careful with timeouts
•

Larger at the routing

•

Smaller the deeper into your app core

Extra logging for belt and braces:
f recoverWith {
case t: TimeoutException => {
log.error(”Alerts API took too long”)
Future.failed(
new TimeoutException(”Alerts API Timeout”)
)
}
}
PROBLEM 3
Actors – We’re doing it wrong!

Good things from Actors:
•

Concurrency and State

•

Supervision Hierarchies
AKKA SURVEY

https://typesafe.com/blog/akka-survey-2013-and-roadmap-update
WHAT TO DO?

http://www.flickr.com/photos/laenulfean/5943132296/
SECOND DESIGN – PER REQUEST ACTOR
App Core

Routing

Clients

tell

tell

ROUTING

PER REQUEST

WISHLIS
T

tell

GET WISHLIST
ITEMS

PRODUCT
tell

ALERT
SUPERVISION HIERARCHY
Routing

App Core

Clients

WISHLIST

ROUTING

PER
REQUEST

GET WISHLIST ITEMS

PRODUCT

A

C

B

ALERTS
TIMEOUTS
App Core

Routing

Clients

WISHLIST

ROUTING

PER
REQUEST

5 sec
GET WISHLIST ITEMS

10
sec

PRODUCT

A

C

B

5 sec

ALERTS
5 sec
DOWNSIDES
•

Performance Impact?
–

Creating Actors is cheap, but not free

–

Benchmark it

–

Horizontally scale out servers
PER REQUEST ACTOR
EXAMPLE APPLICATION

github.com/net-a-porter/spray-actor-perrequest
FLOW:
EMBRACE THE PIPELINES
SPRAY CLIENT PIPELINES

Outgoing requests
• Authentication
WISHLIST
AGGREGATIO
N

WISHLIST

• Internationalisation
• Marshalling
• Logging
• Traceability

• Error handling
SPRAY CLIENT PIPELINES
The Benefits
•

Write your own!

•

Predefined flow of operations

•

Cross-cutting concerns

•

Holistic view of what’s happening
FLOW. EMBRACE THE PIPELINES.
def pipeline(proxyCtx: ProxyContext) = (
keepQueryStringFor(GET, DELETE)
~> whitelistHeaders(allowedHeaders)
~> whitelistQueryString(allowedParams)
~> addHeader("X-Authenticated-User", proxyCtx.userId)
~> requestIdHeader(proxyCtx.requestId)
~> updateHost(scheme, host, port)

~> meToOwner(proxyCtx.userId)
~> logRequest
~> sendReceive(transport)
~> transformConnectionFailure
~> logResponse
~> checkSuccessResponse
~> parseAsJson
DIRECTIVES

Incoming requests

•
WISHLIST
AGGREGATIO
N

Log request

•

Error handling

•

Extract
–

Transaction tracing info

–

Pagination params

•

Authorisation

•

Validation
SPRAY ROUTING DIRECTIVES
•

Keep your routing clean – Write your own directives

•

Cross-cutting concerns (e.g. validation, error handling)

•

Improves readability

•

Holistic view of what’s happening
DIRECTIVES
GET www.net-a-porter.com/newcollection?authToken=TOKEN
path(Segment) { segment => ctx =>
log.info(”Received a request: ” + ctx.request)

val authToken = ctx.request.uri.query.get("authToken")
val isAuthorised = authToken.map(_ == “TOKEN").orElse(Some(false)).get

if (isAuthorised) {
ctx.complete("You have been authorised to access /" + segment)
} else {
ctx.reject(AuthorizationFailedRejection)
}

}
DIRECTIVES
GET www.net-a-porter.com/newcollection?authToken=TOKEN
path(Segment) { segment => ctx =>
logRequest {

val authToken = ctx.request.uri.query.get("authToken")
val isAuthorised = authToken.map(_ == “TOKEN").orElse(Some(false)).get

if (isAuthorised) {
ctx.complete("You have been authorised to access /" + segment)
} else {
ctx.reject(AuthorizationFailedRejection)
}
}
}
DIRECTIVES
GET www.net-a-porter.com/newcollection?authToken=TOKEN
path(Segment) { segment => ctx =>
logRequest {

withAuth {

ctx.complete("You have been authorised to access /" + segment)

}
}
}
DIRECTIVES
logReq {
handleExceptions(myExceptionHandler()) {
handleRejections(myRejectionHandler()) {
respondWithMediaType(`application/json`) {
pathPrefix("api") {
withAuth { authCtx =>
wishlistRoute(authCtx) ~
itemsRoute(authCtx)
}~
withoutAuth { authCtx =>
schemaRoute(authCtx) ~
corsRoute() ~
swaggerDocs()
}
...
}
STRESS IS GOOD

•

Scenarios written in Scala

•

Nice DSL

•

Integration with Jenkins

•

Open Source
STRESS IS GOOD
Example Scenario:
val httpConf = http
.baseURL("http://www.net-a-porter.com")
.disableFollowRedirect

val scn = scenario("NAP homepage")
.exec(
http("NAP desktop homepage")
.get("/")
.queryParam("deviceType", "Desktop")
.check(status.is(200)))

setUp(scn.inject(ramp(10) over (5)))
.protocols(httpConf)
GATLING TIPS
•

Use Gatling 2

•

Start by hitting a specific resource and then run a whole scenario

•

Log your KOs:

val httpConf = http
.baseURL("http://www.net-a-porter.com")
.extraInfoExtractor {
case (KO, _, req, res) => req.getUrl :: res.getResponseBody() :: Nil
case _
}

=> Nil
OPTIMISE
TYPESAFE CONSOLE
PRODUCTION MONITORING
•

Be careful when tools say they support Scala

•

Many Scala frameworks are not supported
–

E.g. Requests are not being tracked if you don’t use one of their
supported HTTP clients.

•

Only very basic information is provided
–

E.g. No knowledge of the actor system
SLICK MASTER SLAVE SUPPORT
•

Routing of database queries:

write
read
•

master
slave

Slick pull request pending

def createWishlist(name: String) = readWrite {
sqlu"INSERT INTO wishlist (name) VALUES ($name)".execute
}

def getWishlist(wishlistId: String) = readOnly {
sql"SELECT * FROM wishlist WHERE id = $wishlistId".as[Wishlist].firstOption
}
OPTIMISE

DISPATCHER
OPTIMISE

?

DISPATCHER
OPTIMISE

DISPATCHER A

DISPATCHER B
TRANSITIONING TO SCALA
TRANSITIONING TO SCALA
•

In-house workshops

•

Coursera courses
–

Functional Programming Principles in Scala

–

Principles of Reactive Programming
CHECK OUT OUR
TECH BLOG

techblog.net-a-porter.com

Scala does the Catwalk