Introduction to Finch

25
Introduction to Finch English edition GitHub @ Saint1991

Transcript of Introduction to Finch

Page 1: Introduction to Finch

Introduction to FinchEnglish edition

GitHub @ Saint1991

Page 2: Introduction to Finch

What is Finch?A combinator library to build up Finagle’s

Service in a functional fashion.

Service

HTTP Server

HTTP ClientFinch

Build using CombinatorsFilter

finagle

≒Request => Future[Response]

Page 3: Introduction to Finch

Why Finch?Unlike Finagle, Finch is specialized to HTTP

Easy routing Rich Utility for HTTP Supporting modern JSON libararies like Circe and Argonaut. Finagle’s strong functionalities like Filter and Server are stil available

Page 4: Introduction to Finch

Differences in Code

GET /div/op1/(int1)/op2/(int2)

{ "result": (int)}=>

An endpoint that retruns the division of int1 by int2

Page 5: Introduction to Finch

Finagle

import io.circe.generic.auto._import io.circe.syntax._case class Res(result: Int)

val service: Service[Request, Response] = RoutingService.byMethodAndPathObject[Request] { case (Get, Root / "div" / "op1" / op1 / "op2" / op2) => new Service[Request, Response] { def apply(request: Request): Future[Response] = Future.value( allCatch withTry { Response(request.version, Status.Ok, Reader.fromBuf( Buf.Utf8( Res(op1.toInt / op2.toInt).asJson.noSpaces ) )) } getOrElse Response(request.version, Status.BadRequest, Reader.fromBuf( Buf.Utf8("Invalid params") )) ) }}

1. RoutingService and Pattern matching are required for routing

1

2. Error handling coexists in a main logic…

2

23

3. JSON serialization is on your own

Page 6: Introduction to Finch

Finch

case class Res(result: Int)

val getDiv: Endpoint[Res] = get( "div" :: "op1" :: int :: "op2" :: int.shouldNot("be 0") { _ == 0 }) { (op1: Int, op2: Int) => Ok(Res(op1 / op2))}

import io.finch.circe._import io.circe.generic.auto._val service: Service[Request, Response] = getDiv.toService

1. No redundant components is required for routing2. Easy to write error handling (It’s separated from main logic)3. Finch internally treats JSON serialization!

Page 7: Introduction to Finch

What is Endpoint in Finch?Implementing on Finch≒ Implementing

Endpoint[A]

Endpoint[A]

val getDiv: Endpoint[Res] = get( "div" :: "op1" :: int :: "op2" :: int.shouldNot("be 0") { _ == 0 }) { (op1: Int, op2: Int) => Ok(Res(op1 / op2))}

Request => Option [ Future[ Output[A] ] ]HTTP Response

Aynchronous procedure(May be in backend)(50x occurs here)

Page 8: Introduction to Finch

Relation to Finagle ServiceFinagle’s service is composed of a combination

of Endpoints serially or parallely

Endpoint 1

Endpoint 3

::

Endpoint 4

:+:

:+:Finagle Service

Endpoint 2

toService

Page 9: Introduction to Finch

How to compose Endpoints? Routing JSON serialization (Circe) Validation for data user input Error Handling

Page 10: Introduction to Finch

How to compose Endpoints? Routing JSON serialization (Circe) Validation for data user input Error Handling

Page 11: Introduction to Finch

RoutingMethod

Utility functions according to each HTTP method are provided

PathUse :: to compose as an Endpoint

Use :+: to compose as different Endpoints

get("div" :: "op1" :: int :: "op2" :: int)

GET /div/op1/(int)/op2/(int)

get("hello" :: string) :+: post("echo" :: string)

GET /hello/(string) POST /echo/(string)

Page 12: Introduction to Finch

Routing - extracting user inputsBuild-in utilities are provided for premitives

Path params• string, long, int, boolean, uuid

Query params• param, paramOption, params, paramsNel

Body• body, bodyOption, binaryBody, binaryBodyOption,

asyncBody

get("div" :: "op1" :: int :: "op2" :: int :: paramOption("pretty").as[Boolean])

GET /div/op1/(int)/op2/(int)[?pretty={true|false}]

etc…

Page 13: Introduction to Finch

Routing - extracting user inputsExtracted parameters are passed to

Endpoint#applyProcess these here to convert to desired

output as a response.get( "div" :: "op1" :: int :: "op2" :: int :: paramOption("pretty").as[Boolean]){ (op1: Int, op2: Int, isPretty: Boolean) => ??? }

Page 14: Introduction to Finch

Column about Routing (1)An example that serially compose two

existing endpoints

val getSumOfProdAndDiv: Endpoint[Int] = get(getProd :: getDiv) {    (product: Int, division: Int) =>  Ok(product.result + division.result)}

val getDiv: Endpoint[Int] = get("div" :: "op1" :: int :: "op2" :: int) { (op1: Int, op2: Int) => Ok(op1 / op2)}

val getProd: Endpoint[Int] = get("prod" :: "op1" :: int :: "op2" :: int) { (op1: Int, op2: Int) => Ok(op1 * op2)}

GET /prod/op1/(int)/op2/(int)/div/op1/(int)/op2/(int)

Don’t combine endpoints of different HTTP method.It does not match any requests!

Page 15: Introduction to Finch

Column about Routing (2)Combination by :+: is ordered

val hello: Endpoint[String] = get("hello" :: string) { (str: String) => Ok(s"Hello $str!!!") }

val helloBar: Endpoint[String] = get("hello" :: "bar") { Ok("bar")}

hello :+: helloBarGiven

GET /hello/bar => “Hello bar!!!”(1)

helloBar :+: hello

GET /hello/bar => “bar”(2)

Match is checked from head to tail in a combination order

Given

Page 16: Introduction to Finch

How to compose Endpoints? Routing JSON serialization (Circe) Validation for data user input Error Handling

Page 17: Introduction to Finch

JSON Serialization (Circe)What you have to do are only

Define implicit Encoder[A] within the scope of toService

Import io.finch.circe._ within the scope of toServicecase class Res(result: Int)

object CalcService { val getDiv: Endpoint[Res] = get("div" :: "op1" :: int :: "op2" :: int) { (op1: Int, op2: Int) => Ok(Res(op1 / op2)) }}

object ServerApp extends TwitterServer { import io.finch.circe._ import io.circe.generic.auto._ val endpoints = CalcService.getDiv.toService . . .}

Not here…

IT’S HERE!!!

※ With Circe, importing io.circe.generic.auto._ This generates Encoder of any case classes !!!

Page 18: Introduction to Finch

Not JSON ResponseAll responses are serialized as

application/json. It’s sometimes not convenient…val hello: Endpoint[String] = get("hello" :: string) {

who: String => Ok(s"Hello $who")}

GET /hello/bar => ”Hello bar”Response value is wrapped with ”” …

val helloPlain: Endpoint[Response] = get("helloPlain" :: string) { who: String => val res = Response() res.setContentType("text/plain") res.setContentString(s"Hello $who") Ok(res)}

GET /helloPlain/bar => Hello barTo avoid it, turn the response type of Endpoint

to Buf or Reponse and set Content-Type

Page 19: Introduction to Finch

How to compose Endpoints? Routing JSON serialization (Circe) Validation for data user input Error Handling

Page 20: Introduction to Finch

Validation for User InputsIt can be put with any extractors .

get("div" :: "op1" :: int :: "op2" :: int.shouldNot("be 0") {_ == 0})

=> NotValid will be thrown when provided 0 to the second param

Validation codes can be separated from main logic!!!

※Finch responds with BadRequest when NotValid is thrown ,

Page 21: Introduction to Finch

How to compose Endpoints? Routing JSON serialization (Circe) Validation for data user input Error Handling

Page 22: Introduction to Finch

Error HandlingAny not handled exceptions can be caught in

a “handle” clause When some exceptions are not handle, Finch

responds with InternalServerError with an empty payload…

val getDiv: Endpoint[Res] = get( "div" :: "op1" :: int :: "op2" :: int) { (op1: Int, op2: Int) => Ok(Res(op1 / op2))} handle { case ae: ArithmeticException => BadRequest(ae)}

Page 23: Introduction to Finch

Error HandlingWhen you want to responds with your custom

Exception , you have to override Encoder[Exception] that is defined in Finch as a defaultcase class ErrorRes(errorCode: Int, message: String) extends Exception

import io.circe.syntax._ import io.finch.circe._import io.circe.generic.auto._ implicit val errorEncoder: Encoder[Exception] = Encoder.instance { case er: ErrorRes => er.asJson}

val endpoints = CalcService.getDiv.toService

※ It’s override. If you don’t specify the case of Finch build-in exceptions like NotValid, NotPresent, NotParsed, etc…) are not handled. In practice you had better care those cases.

!

Page 24: Introduction to Finch

Entire code (some imports are omitted)

case class Res(result: Int)case class ErrorRes(errorCode: Int, message: String) extends Exception

object CalcService { val getDiv: Endpoint[Res] = get("div" :: "op1" :: int :: "op2" :: int) { (op1: Int, op2: Int) => Ok(Res(op1 / op2)) } handle { case ae: ArithmeticException => BadRequest(ErrorRes(400, ae.getMessage)) }}

object ServerApp extends TwitterServer {

import io.finch.circe._ import io.circe.generic.auto._ import io.circe.syntax._

implicit val errorEncoder: Encoder[Exception] = Encoder.instance { case er: ErrorRes => er.asJson } val endpoints = CalcService.getDiv.toService

val server: ListeningServer = Http.server.serve(":8000", endpoints) onExit(Await.ready(server.close(30 seconds))) Await.ready(server)}

Page 25: Introduction to Finch

Pros. and Cons.Pros.

Easy routingExcellent JSON support.Maybe easy to test

Cons.10% overheadHave to pay attension to the scope of JSON

EncodersIntelliJ…