ºÝºÝߣ

ºÝºÝߣShare a Scribd company logo
Finch + Finagle-OAuth2
=
Purely Functional REST API
Vladimir Kostyukov
@vkostyukov
Finagle-OAuth2: Highlights
? Asynchronous version of Nulab¡¯s scala-oauth2-provider
!
? Finagle-friendly API: OAuth2Request, OAuth2Filter	
!
? Does not depend on Finch
2
1 trait DataHandler[U] {	
2 ... 	
3 def findUser(username: String, password: String): Future[Option[U]]	
4 def createAccessToken(authInfo: AuthInfo[U]): Future[AccessToken]	
5 def getStoredAccessToken(authInfo: AuthInfo[U]): Future[Option[AccessToken]]	
6 def findAccessToken(token: String): Future[Option[AccessToken]]	
7 ...	
8 }	
Finagle-OAuth2: Step #1
DataHandler
3
Finagle-OAuth2: Step #2
Typesafe Auth
1 import com.twitter.finagle.oauth2._	
2 import com.twitter.finagle.oauth2.{OAuth2Filter, OAuth2Request}	
3 	
4 val auth = new OAuth2Filter(dataHandler)	
5 	
6 val hello = new Service[OAuth2Request[User], Response] {	
7 def apply(req: OAuth2Request[User]) = {	
8 println(s"Hello, ${req.authInfo.user}!")	
9 Future.value(Response())	
10 }	
11 }	
12 	
13 val backend: Service[Request, Response] = auth andThen hello	
4
1 import com.twitter.finagle.oauth2._	
2 import com.twitter.finagle.oauth2.OAuth2Endpoint	
3 	
4 val tokens: Service[Request, Response] = 	
5 new OAuth2Endpoint(dataHandler) with OAuthErrorInJson with OAuthTokenInJson	
Finagle-OAuth2: Step #3
Issue Acces Token
5
Thin layer of purely-functional basic blocks
on top of Finagle for building composable
REST APIs
Finch
https://github.com/?nagle/?nch
6
Finch: Highlights
? Was #1 Scala trending repo on GitHub for 95 mins
!
!
!
!
!
!
!
? Happily used in production by 2 customers:
? Konfettin
? JusBrasil
!
? Super simple & lightweight (~ 1.5k SLOC)
7
Finch: Quickstart
1 def hello(name: String) = new Service[HttpRequest, HttpResponse] = {	
2 def apply(req: HttpRequest) = for {	
3 title <- OptionalParam("title")(req)	
4 } yield Ok(s"Hello, ${title.getOrElse("")} $name!")	
5 }	
6 	
7 val endpoint = new Endpoint[HttpRequest, HttpResponse] {	
8 def route = {	
9 // routes requests like '/hello/Bob?title=Mr.'	
10 case Method.Get -> Root / "hello" / name => 	
11 BasicallyAuthorize("user", "password") ! hello(name)	
12 }	
13 }	
14 	
15 val service: Service[HttpRequest, HttpResponse] = endpoint.toService 	
8
Finch: Request Reader
(Reader Monad)
1 trait RequestReader[A] {	
2 	
3 def apply(req: HttpRequest): Future[A]	
4 	
5 def flatMap[B](fn: A => RequestReader[B]): RequestReader[B] = ???	
6 	
7 def map[B](fn: A => B): RequestReader[B] = ???	
8 }	
9
Finch: Request Reader
1 val pagination: RequestReader[(Int, Int)] = for {	
2 offset <- OptionalIntParam("offset")	
3 limit <- OptionalIntParam("limit")	
4 } yield (offset.getOrElse(0), math.min(limit.getOrElse(50), 50))	
5 	
6 val service = new Service[HttpRequest, HttpResponse] {	
7 def apply(req: HttpRequest) = for {	
8 (offsetIt, limit) <- pagination(req)	
9 } yield Ok(s"Fetching items $offset..${offset+limit}")	
10 }	
10
Finch: Params Validation
1 case class User(age: Int)	
2 	
3 val user: RequestReader[User] = 	
4 for { age <- RequiredIntParam("age") } yield User(age)	
5 	
6 val adult: RequestReader[User] = for {	
7 u <- user	
8 _ <- ValidationRule("age", "should be greater then 18") { user.age > 18 }	
9 } yield u	
10 	
11 val u: Future[JsonResponse] = adult(request) map { 	
12 JsonObject("age" -> _.age) 	
13 } handle {	
14 case e: ParamNotFound =>	
15 JsonObject("error" -> e.getMessage, "param" -> e.param)	
16 case e: ValidationFailed => 	
17 JsonObject("error" -> e.getMessage, "param" -> e.param)	
18 }	
11
Finch:
Request Reader Highlights
? RequiredParam, RequiredIntParam, etc 	
? Future.value[A]	
? Future.exception[ParamNotFound]
!
? OptionalParam, OptionalIntParam, etc	
? Future.value[Option[A]]
!
? Multi-value Params: RequiredParams, OptionalIntParams, etc.
? Future.value[List[A]]
!
? Params Validation: ValidationRule	
? Future.value[Unit]	
? Future.exception[ValidationFailed]
12
Finch: Responses
1 // empty response with status 200	
2 val a = Ok()	
3 	
4 // 'plain/text' response with status 404	
5 val b = NotFound("body")	
6 	
7 // 'application/json' response with status 201	
8 val c = Created(JsonObject("id" -> 100))	
9 	
10 // ¡®plain/text' response with custom header and status 403	
11 val d = Forbidden.withHeaders("Some-Header" -> "Secret")("body") 	
13
Finch: Endpoints Highlights
? Finch¡¯s Endpoints are composable routers
!
? Endpoints might be treated as Scala¡¯s
PartialFunctions[Request, Service[_, _]]
!
? Endpoints are convertible to Finagle Service¡¯s
!
? Endpoints might be composed with Service¡¯s, Filter¡¯s or
other Endpoint¡¯s.
14
Finch:
Endpoints Composition
1 val ab: Filter[A, C, B, C] = ???	
2 val bc: Endpoint[B, C] = ???	
3 val cd: Service[C, D]	
4 	
5 val ad1: Endpoint[A, D] = ab ! bc ! cd	
6 val ad2: Endpoint[A, D] = ???	
7 	
8 val ad3: Endpoint[A, D] = ad1 orElse ad2	
15
Finagle rocks!
16
? A better JSON
? Lightweight in-memory caching API
Finch: Further Steps
17
? https://github.com/?nagle/?nch
? https://github.com/?nagle/?nagle-oauth2
Resources
@vkostyukov
18

More Related Content

Finch + Finagle OAuth2

  • 1. Finch + Finagle-OAuth2 = Purely Functional REST API Vladimir Kostyukov @vkostyukov
  • 2. Finagle-OAuth2: Highlights ? Asynchronous version of Nulab¡¯s scala-oauth2-provider ! ? Finagle-friendly API: OAuth2Request, OAuth2Filter ! ? Does not depend on Finch 2
  • 3. 1 trait DataHandler[U] { 2 ... 3 def findUser(username: String, password: String): Future[Option[U]] 4 def createAccessToken(authInfo: AuthInfo[U]): Future[AccessToken] 5 def getStoredAccessToken(authInfo: AuthInfo[U]): Future[Option[AccessToken]] 6 def findAccessToken(token: String): Future[Option[AccessToken]] 7 ... 8 } Finagle-OAuth2: Step #1 DataHandler 3
  • 4. Finagle-OAuth2: Step #2 Typesafe Auth 1 import com.twitter.finagle.oauth2._ 2 import com.twitter.finagle.oauth2.{OAuth2Filter, OAuth2Request} 3 4 val auth = new OAuth2Filter(dataHandler) 5 6 val hello = new Service[OAuth2Request[User], Response] { 7 def apply(req: OAuth2Request[User]) = { 8 println(s"Hello, ${req.authInfo.user}!") 9 Future.value(Response()) 10 } 11 } 12 13 val backend: Service[Request, Response] = auth andThen hello 4
  • 5. 1 import com.twitter.finagle.oauth2._ 2 import com.twitter.finagle.oauth2.OAuth2Endpoint 3 4 val tokens: Service[Request, Response] = 5 new OAuth2Endpoint(dataHandler) with OAuthErrorInJson with OAuthTokenInJson Finagle-OAuth2: Step #3 Issue Acces Token 5
  • 6. Thin layer of purely-functional basic blocks on top of Finagle for building composable REST APIs Finch https://github.com/?nagle/?nch 6
  • 7. Finch: Highlights ? Was #1 Scala trending repo on GitHub for 95 mins ! ! ! ! ! ! ! ? Happily used in production by 2 customers: ? Konfettin ? JusBrasil ! ? Super simple & lightweight (~ 1.5k SLOC) 7
  • 8. Finch: Quickstart 1 def hello(name: String) = new Service[HttpRequest, HttpResponse] = { 2 def apply(req: HttpRequest) = for { 3 title <- OptionalParam("title")(req) 4 } yield Ok(s"Hello, ${title.getOrElse("")} $name!") 5 } 6 7 val endpoint = new Endpoint[HttpRequest, HttpResponse] { 8 def route = { 9 // routes requests like '/hello/Bob?title=Mr.' 10 case Method.Get -> Root / "hello" / name => 11 BasicallyAuthorize("user", "password") ! hello(name) 12 } 13 } 14 15 val service: Service[HttpRequest, HttpResponse] = endpoint.toService 8
  • 9. Finch: Request Reader (Reader Monad) 1 trait RequestReader[A] { 2 3 def apply(req: HttpRequest): Future[A] 4 5 def flatMap[B](fn: A => RequestReader[B]): RequestReader[B] = ??? 6 7 def map[B](fn: A => B): RequestReader[B] = ??? 8 } 9
  • 10. Finch: Request Reader 1 val pagination: RequestReader[(Int, Int)] = for { 2 offset <- OptionalIntParam("offset") 3 limit <- OptionalIntParam("limit") 4 } yield (offset.getOrElse(0), math.min(limit.getOrElse(50), 50)) 5 6 val service = new Service[HttpRequest, HttpResponse] { 7 def apply(req: HttpRequest) = for { 8 (offsetIt, limit) <- pagination(req) 9 } yield Ok(s"Fetching items $offset..${offset+limit}") 10 } 10
  • 11. Finch: Params Validation 1 case class User(age: Int) 2 3 val user: RequestReader[User] = 4 for { age <- RequiredIntParam("age") } yield User(age) 5 6 val adult: RequestReader[User] = for { 7 u <- user 8 _ <- ValidationRule("age", "should be greater then 18") { user.age > 18 } 9 } yield u 10 11 val u: Future[JsonResponse] = adult(request) map { 12 JsonObject("age" -> _.age) 13 } handle { 14 case e: ParamNotFound => 15 JsonObject("error" -> e.getMessage, "param" -> e.param) 16 case e: ValidationFailed => 17 JsonObject("error" -> e.getMessage, "param" -> e.param) 18 } 11
  • 12. Finch: Request Reader Highlights ? RequiredParam, RequiredIntParam, etc ? Future.value[A] ? Future.exception[ParamNotFound] ! ? OptionalParam, OptionalIntParam, etc ? Future.value[Option[A]] ! ? Multi-value Params: RequiredParams, OptionalIntParams, etc. ? Future.value[List[A]] ! ? Params Validation: ValidationRule ? Future.value[Unit] ? Future.exception[ValidationFailed] 12
  • 13. Finch: Responses 1 // empty response with status 200 2 val a = Ok() 3 4 // 'plain/text' response with status 404 5 val b = NotFound("body") 6 7 // 'application/json' response with status 201 8 val c = Created(JsonObject("id" -> 100)) 9 10 // ¡®plain/text' response with custom header and status 403 11 val d = Forbidden.withHeaders("Some-Header" -> "Secret")("body") 13
  • 14. Finch: Endpoints Highlights ? Finch¡¯s Endpoints are composable routers ! ? Endpoints might be treated as Scala¡¯s PartialFunctions[Request, Service[_, _]] ! ? Endpoints are convertible to Finagle Service¡¯s ! ? Endpoints might be composed with Service¡¯s, Filter¡¯s or other Endpoint¡¯s. 14
  • 15. Finch: Endpoints Composition 1 val ab: Filter[A, C, B, C] = ??? 2 val bc: Endpoint[B, C] = ??? 3 val cd: Service[C, D] 4 5 val ad1: Endpoint[A, D] = ab ! bc ! cd 6 val ad2: Endpoint[A, D] = ??? 7 8 val ad3: Endpoint[A, D] = ad1 orElse ad2 15
  • 17. ? A better JSON ? Lightweight in-memory caching API Finch: Further Steps 17