1. REST on Akka
!
Antoine Comte
Twitter : @comte_a
antoine.comte@gmail.com
Slidedeck courtesy of the Spray team
2. What is spray?
Suite of libraries for building and consuming
RESTful web services on top of Akka
• First released about 2 year ago
• Principles: lightweight, async, non-blocking,
actor-based, modular, few deps, testable
• Philosophy: library, not framework
3. Components
• Rich immutable HTTP model
• spray-server:
DSL for server-side API construction
• spray-client: complementary HTTP client
• spray-can: low-level HTTP server and client
• spray-json: straight JSON in scala (no Akka)
4. spray-server
• Runs on servlet containers or spray-can
• Tool for building a “self-contained” API layer
• Central element:
Routing DSL for defining web API behavior
• Focus: RESTful web API, not web GUI
7. Basic Architecture
REST API layer
HTTP Request
Action
Routing
Logic
HTTP Response
Application
domain
object !
Reply
Business
Logic
8. API Layer Responsibilities
• Request routing based on method, path,
query parameters, entity
• (Un)marshalling to / from domain objects
• Encoding / decoding
• Authentication / authorization
• Caching and serving static content
• RESTful error handling
9. Route Example
A simple spray route:
!
val route: Route =
path("order" / HexIntNumber) { id =>
get {
completeWith {
"Received GET request for order " + id
}
} ~
put {
completeWith {
"Received PUT request for order " + id
}
}
}
10. Routing Basics
Routes in spray:
type Route = RequestContext => Unit
!
Central object:
case class RequestContext(
request: HttpRequest,
...) {
def complete(...) { ... }
def reject(...) { ... }
...
}
Explicit
continuation-
passing style
11. Routing Basics
The simplest route:
ctx => ctx.complete("Say hello to spray")
or:
_.complete("Say hello to spray")
or using a “directive”:
completeWith("Say hello to spray")
def completeWith[T :Marshaller](value: => T): Route =
_.complete(value)
12. Directives
Route structure built with directives:
!
val route: Route =
path("order" / HexIntNumber) { id =>
get {
completeWith {
"Received GET request for order " + id
}
} ~
put {
completeWith {
"Received PUT request for order " + id
}
}
}
extractions
directive
name
args
route concatenation:
recover from rejections
Route structure
forms a tree!
inner route
13. Directives
Compiles?
Operators are type-safe:
val orderPath = path("order" / IntNumber)
val dir = orderPath | get
val dir = orderPath | path("[^/]+".r / DoubleNumber)
val dir = orderPath | parameter('order.as[Int])
val order = orderPath & parameters('oem, 'expired ?)
val route = order { (orderId, oem, expired) =>
... // inner route
}
15. Real World Example
lazy val route = {
encodeResponse(Gzip) {
path("") {
get {
redirect("/doc")
}
} ~
pathPrefix("api") {
jsonpWithParameter("callback") {
path("top-articles") {
get {
parameter('max.as[Int]) { max =>
validate(max >= 0, "query parameter 'max' must be >= 0") {
completeWith {
(topArticlesService ? max).mapTo[Seq[Article]]
}
}
}
}
} ~
tokenAuthenticate { user =>
path("ranking") {
get {
countAndTime(user, "ranking") {
parameters('fixed ? 0, 'mobile ? 0, 'sms ? 0, 'mms ? 0,
16. Best Practices
• Keep route structure clean and readable,
pull out all logic into custom directives
• Don’t let API layer leak into application
• Use (Un)marshalling infrastructure
• Wrap blocking code with `detach`
• Use sbt-revolver + JRebel for fast dev turnaround
17. There is more ...
• SprayJsonSupport, LiftJsonSupport,
TwirlSupport, ScalateSupport
• Asynchronous response push streaming
• Testing spray routes
• RESTful errors
• spray-client
18. Current State
• spray 1.2-RC2 just released
• Coming features: new documentation site,
deeper REST support, monitoring, request
throttling, and more ...