Finch: 0.1 to 1.0

Vladimir Kostyukov

@vkostyukov

In the Very Beginning I wanted ...

  1. RESTful API with Finagle

Finagle HTTP(x)

Finagle (Micro)service

def getUser(userId: Int) = new Service[HttpRequest, HttpResponse] = {
  def apply(req: HttpRequest): Future[HttpResponse] = {
    val n = req.params.get("name").getOrElse("n/a")
    val rep = Response(Status.Ok)
    rep.setContentTypeJson()
    rep.setContentString(s"""{"name":"$n", "id": $id}""")

    Future.value(rep)
  }
}

Finagle (Micro)service

def getUser(userId: Int) = new Service[HttpRequest, HttpResponse] = {
  def apply(req: HttpRequest): Future[HttpResponse] = {
    val n = req.params.get("name").getOrElse("n/a")
    val rep = Response(Status.Ok)
    rep.setContentTypeJson()
    rep.setContentString(s"""{"name":"$n", "id": $id}""")

    Future.value(rep)
  }
}

Finagle (Micro)service

def getUser(userId: Int) = new Service[HttpRequest, HttpResponse] = {
  def apply(req: HttpRequest): Future[HttpResponse] = {
    val n = req.params.get("name").getOrElse("n/a")
    val rep = Response(Status.Ok)
    rep.setContentTypeJson()
    rep.setContentString(s"""{"name":"$n", "id": $id}""")

    Future.value(rep)
  }
}

Finagle Router & Server

val router: Service[HttpRequest, HttpResponse] = 
  RoutingService.byPathObject {
    case Root / "users" / Integer(id) => getUser(id)
    // case Root / "tickets" / id => getTicket(id)
  }

Http.serve("8080", router)

Finagle Router & Server

val router: Service[HttpRequest, HttpResponse] = 
  RoutingService.byPathObject {
    case Root / "users" / Integer(id) => getUser(id)
    // case Root / "tickets" / id => getTicket(id)
  }

Http.serve("8080", router)

Finch: REST APIs with Finagle

Happy Birthday, Finch!

Finch = Finagle + inch(1 inch = 0.0254 m)

It's a bird!

`

Endpoints

trait Endpoint[A, B] {
  def route: PartialFunction[(HttpMethod, Path), Service[A, B]]
  def orElse(that: Endpoint[A, B]): Endpoint[A, B] = ???
}

Endpoints Usage

val users = Endpoint[HttpRequest, HttResponse] {
  case Get -> Root / "users" => ???
}
val tickets = Endpoint[HttpRequest, HttResponse] {
  case Get -> Root / "tickets" => ???
}

RequestReaders

trait RequestReader[A] {
  def apply(req: HttpRequest): Future[A]

  def flatMap[B](fn: A => RequestReader[B]): RequestReader[B]
  def map[B](fn: A => B): RequestReader[B]
}

Built-in RequestReaders

def RequiredParam(name: String): RequestReader[String] = ???
def OptionalParam(name: String): RequestReader[Option[String]] = ???
def RequeredIntParam(name: String): RequestReader[Int] = ???
def OptionalItParam(name: String): RequestReader[Int] = ???
....

RequestReaders Usage

case class User(id: Int, name: String)

val user: RequestReader[User] = for {
  id <- RequiredIntParam("id")
  name <- OptionalParam("name")
} yield User(id, name.getOrElse("n/a"))

ValidationRules

def ValidationRule(rule: String)(predicate: => Boolean) = 
  new RequestReader[Unit] {
    def apply(req: HttpRequest) =
      if (predicate) Future.Done
      else Future.exception(ValidationFailed(rule))
  }

ValidationRules Usage

case class User(age: Int)

val user: RequestReader[User] = RequiredIntParam("age").map(User)

val adult: RequestReader[User] = for {
  u <- user
  _ <- ValidationRule("age should be greater than 21") { u.age > 21 }
} yield u

ValidationRules Usage

case class User(age: Int)

val user: RequestReader[User] = RequiredIntParam("age").map(User)

val adult: RequestReader[User] = for {
  u <- user
  _ <- ValidationRule("age should be greater than 21") { u.age > 21 }
} yield u

ResponseBuilders

case class ResponseBuilder(status: HttpResponseStatus) {
  def apply(plain: String): HttpResponse  = ???
  def apply(json: JSONType): HttpResponse = ???
  def apply(): HttpResponse = ???
}

ResponseBuilders Usage

val ok: HttpResponse = Ok("plain/text")
val notFound: HttpResponse = NotFound(JSONObject(Map("id" -> 100)))
val noContent: HttpResponse = NoContent()


EncodeResponse

trait EncodeResponse[-A] {
  def apply(rep: A): String
  def contentType: String
}
object EncodeResponse {
  def apply[A](ct: String)(f: String => A): EncodeResponse[A] = ???

  implicit val encodeString: EncodeResponse[String] = 
    EncodeResponse("plain/text")(s => s)
}

ResponseBuilder

case class ResponseBuilder(status: Status) {
  def apply[A: EncodeResponse](body: A): HttpResponse = ???
}

ResponseBuilder & JSON

import io.finch.argonaut._, argonaut._, Argonaut._
val a: HttpResponse = Ok(Json.obj("id" -> jNumber(100)))

import io.finch.jackson._
case class User(id: Int, name: String)
val b: HttpResponse = Ok(User(100, "Ivan"))

Body Readers

val RequiredBody: RequestReader[String] = ???
val OptionalBody: RequestReader[Option[String]] = ???

val user: RequestReader[User] = RequiredBody.map { body =>
  // TODO parse User from String
}

DecodeRequest

trait DecodeRequest[+A] {
  def apply(req: String): Try[A]
 }

 object DecodeRequest {
   def apply[A](f: String => Try[A]): DecodeRequest[A] = ???

   implicit val decodeString: DecodeRequest[String] = 
     DecodeRequest(s => Return(s))
}

Body Readers (Improved)

def RequiredBody[A: DecodeRequest]: RequestReader[A] = ???
def OptionalBody[A: DecodeRequest]: RequestReader[Option[A]] = ???


RequestReader & JSON

import io.finch.argonaut._, argonaut._, Argonaut._
val a: RequestReader[Json] = RequiredBody[Json]

import io.finch.jackson._
case class User(id: Int, name: String)
val b: RequestReader[Option[User]] = OptionalBody[User]

Decode all the things!

implicit val decodeInt: DecodeRequest[Int] = 
  DecodeRequest { s => Try(s.toInt) }

implicit val decodeLong: DecodeRequest[Long] = 
  DecodeRequest { s => Try(s.toLong) }

implicit val decodeBoolean: DecodeRequest[Boolean] = 
  DecodeRequest { s => Try(s.toBoolean) }
...

Decode all the things!

implicit class RROps[R](val rr: RequestReader[String]) {
  def as[A: DecodeRequest]: RequestReader[A] = ???
}


Decode all the things!

implicit class RROps[R](val rr: RequestReader[String]) {
  def as[A: DecodeRequest]: RequestReader[A] = ???
}

val a = RequiredIntParam("a")
val b = RequiredBooleanParam("b")
val c = RequredBody[Json]

Decode all the things!

implicit class RROps[R](val rr: RequestReader[String]) {
  def as[A: DecodeRequest]: RequestReader[A] = ???
}

val a = RequiredParam("a").as[Int]
val b = RequiredParam("b").as[Boolean]
val c = RequredBody.as[Json]

Decode all the things!

implicit class RROps[R](val rr: RequestReader[String]) {
  def as[A: DecodeRequest]: RequestReader[A] = ???
}

val a = param("a").as[Int]
val b = param("b").as[Boolean]
val c = body.as[Json]

Monadic RequestReader

case class User(age: Int, name: String, city: Option[String])

val user: RequestReader[User] = for {
  age <- param("age").as[Int]
  name <- param("name")
  city <- paramOption("city").withDefault("San Francisco")
} yield User(age, name, city)

Do we always
need a Monad?

Applicative RequestReader

case class ~[A, B](a: A, b: B)

trait RequestReader[A] {
  def ~[B](that: RequestReader[B]): RequestReader[A ~ B] = ???
}

Applicative RequestReader

case class User(age: Int, name: String, city: Option[String])

val a: RequestReader[Int ~ String ~ String] =
  param("age").as[Int] ~
  param("name") ~
  paramOption("city").withDefault("San Francisco")

val b: RequestReader[User] = a.map {
  case age ~ name ~ city => User(age, name, city)
}

Applicative RequestReader

case class User(age: Int, name: String, city: Option[String])

val a: RequestReader[Int ~ String ~ String] =
  param("age").as[Int] ~
  param("name") ~
  paramOption("city").withDefault("San Francisco")

val b: RequestReader[User] = a.map {
  case age ~ name ~ city => User(age, name, city)
}

Applicative RequestReader

case class User(age: Int, name: String, city: Option[String])

val a: RequestReader[Int ~ String ~ String] =
  param("age").as[Int] ~
  param("name") ~
  paramOption("city").withDefault("San Francisco")

val b: RequestReader[User] = a ~> User

Combinator ~>

Can we treat A ~ B ~ .. ~ Z => \_(ツ)_/ as (A, B, .., Z) => \_(ツ)_/?

implicit class Arrow26(rr: RequestReader[A ~ B ~ .. ~ Z]) {
  def ~>[A0](f: (A, B, .., Z) => A0): RequestReader[A0] =
    rr.map {
      case a ~ b ~ .. ~ z => f(a, b, ..., z)
    }
}

Routers

case class /[A, B](a: A, b: B)

trait Router[+A] {
  def apply(r: Route): Option[(Route, A)]

  def map[B](f: A => B): Router[B] // or />
  def andThen[B](that: Router[B]): Router[A / B] // or /
  def orElse[B >: A](that: Router[B]): Router[B] // or |
}

Built-in Routers

object Get extends Router[Nothing] { ... }
object Post extends Router[Nothing] { ... }
object Patch extends Router[Nothing] { ... }
object * extends Router[Nothing] { ... }
...
object int extends Router[Int] { ... }
object string extends Router[String] { ... }
object boolean extends Router[Boolean] { ... }
...

Implicit Routers

implicit def intToRouter(i: Int): Router[Nothing] = ???
implicit def stringToRouter(s: String): Router[Nothing] = ???
implicit def booleanToRouter(b: Boolean): Router[Nothing] = ???

Routers Usage

case class User(id: Int)

val a: Router[User] = Get / ("users" | "user") / int("id") /> User
val b: Router[HttpResponse] = * / "health" /> Ok()


Endpoints v2.0

type Endpoint[A, B] = Router[Service[A, B]]
type Api = Endpoint[HttRequest, HttResponse]

val a: Api = Get / "users" / int /> GetUserService
val b: Api = Get / "tickets" / int /> GetTicketsService


What's next? (0.7 ... 1.0)

  1. Heterogeneous Routers (powered by Shapeless)

https://github.com/finagle/finch