Post on 13-Feb-2017
transcript
TYPED SERVICES USING FINCHTom Adams, @tomjadams
YOW West 2016
1 SERVICES
SERVICE
• We care about things like
• HTTP primitives
• Request/response encode/decode
• Transport protocols
• Talking to downstream services
• Local data storage
• But not these
• Asset packaging
• View rendering
• JavaScript, SASS, LESS, etc.
LANDSCAPE
• Go: gokit, Kite
• Elixir: Phoenix
• Javascript: Node.js (TypeScript)
• Clojure: Caribou, Liberator, Rook
• Ruby: Rails, Sinatra, Grape, Lotus
• Erlang: Leptus, Yaws
• Haskell: Snap, rest, servant, Hapstack, Yesod
• Java: Play, Spring, Jersey, Spark, RESTEasy, (Dropwizard)
• Swift: Swifton, Vapor
WHAT’S IN A HTTP FRAMEWORK?
WE NEED
1. Routing
• path/headers/methods/etc. to a function
2. Req → Resp
SERVICE PRIMITIVES
• Routing is a function
• r :: URI → a
• An “action” is a function
• a :: Req → Resp
• A “controller” is (scoped) a collection of actions
• c :: List a
• A “service” is a collection of controllers
• s :: List c
BUT WAIT, THERE’S MORE
• HTTP primitives
• Datastore
• Metrics
• Logging
• JSON codec
• Databinding
• Configuration
• Environments
• HTTP clients
• Failure isolation
• Async primitives
• Monitoring
• Service discovery
• Debugging/tracing
• Caching
• Messaging
• Deployment
• Testing
• Live code reload
• …
2 SCALALet’s talk about Scala
https://github.com/OlegIlyenko/scala-icon
WHY SCALA?
• JVM support - “Better Java”
• Fast, scalable
• Deployment & runtime behaviour well understood
• Library & tool support (distributed heap, debugging, etc.)
• Decent (not great) static type system
• Succinct - closures, type classes, type aliases, type inference, no semi-colons
• Features - immutability, equational reasoning, functions, case classes, implicits, packages, mixins, currying/partial application, etc.
• Standard library - option, either, future, etc.
• Cool stuff! scalaz, actors, higher-kinds, etc.
WELL USED
• Twitter, Pinterest, Airbnb, SoundCloud, Uber, Strava, Gilt, LinkedIn, Amazon, Tumblr, Foursquare, Box, Gigya, Simple, Localytics, LivingSocial, eHarmony, Yammer, Firebase, Disqus, Asana, Hootsuite, PagerDuty, Rdio, Mesosphere
• Apple, Novell, The Guardian, Sony, BSkyB, AOL, Xerox, Siemens, VMware
• REA, Seek, Skedulo, CBA, Atlassian, Fairfax, RedBalloon, Canva*, Oomph*
Source: Quora, AngelList, scala-lang.org, reddit, LinkedIn, Finagle Adopters
WHY FP?
• (Static) Types, and
• Immutability, and
• Composition, gives rise to
• Equational reasoning, and
• Certainty, and
• Reliability
FRAMEWORK OPTIONS
• Karyon (Netflix)
• Play (Typesafe/Lightbend)
• Unfiltered (OSS)
• Dropwizard (Yammer)
• Spray (Typesafe/Lightbend)
• Finagle (Twitter) / Finatra (OSS) / Finch (OSS)
• Akka, Lagom (Typesafe/Lightbend)
• Colossus (Tumblr)
• Chaos (Mesosphere)
• http4s (OSS)
PERFORMANCE
3 FINCH
FINCH
Finch is a thin layer of purely functional basic blocks on top of Finagle for building HTTP APIs.
It provides developers with simple and robust HTTP primitives, while being as close as possible to the bare metal Finagle API.
HELLO, WORLD
val service = new Service[Request, Response] { def apply(req: Request) { request.path match { case "/hello" => val resp: Response = Response() resp.content = Buf.Utf8("Hello, World!") Future.value(resp) case _ => Future.value(Response()) }}
Http.server.serve(":8080", service)
HELLO, WORLD
import io.finch._import com.twitter.finagle.Http
val api: Endpoint[String] = get("hello") { Ok("Hello, World!") }
Http.server.serve(“:8080", api.toService)
HELLO, WORLD
import io.finch._import com.twitter.finagle.Http
val api: Endpoint[String] = get("hello") { Ok("Hello, World!") }
Http.server.serve(“:8080", api.toService)
Finch
Finagle
FINCH FEATURES
• High level abstraction on top of Finagle (don’t need to drop down to Finagle*)
• Small footprint
• Flexible use (what you make of it)
• Referentially transparent & compositional
• Request / response decoding / encoding
• Explicit async modelling
FINAGLE
A fault tolerant, protocol-agnostic, extensible RPC system for the JVM, used to construct high-concurrency servers.
Finagle implements uniform client and server APIs for several protocols, and is designed for high performance and concurrency.
FINAGLE FEATURES
• Connection pools (w/ throttling)
• Failure detection
• Failover strategies
• Load-balancers
• Back-pressure
• Statistics, logs, and exception reports
• Distributed tracing (Zipkin)
• Service discovery (ZooKeeper)
• Sharding strategies
• Config
TWITTERSERVER
• Lightweight server template
• Command line args
• HTTP admin server
• Logging
• Tracing
• Metrics
• System stats
WHAT DOES THAT MEAN FOR YOU?
• Performance & scalability out of the box
• Maturity of a battle tested framework
• Fast ramp up
• Won’t bottom out as you scale
• Known deployment, monitoring, runtime, etc.
4 CORE FINCH CONCEPTS
TRIUMVIRATE
• Endpoint
• Filters
• Futures
• (Services)
ENDPOINT
• A function that takes a request & returns a value
• Automatically handles Future/async
• Provides routing behaviour
• Extracts/matches values from the request
• Values are serialised to the HTTP response
• Composable (applicative)
EXAMPLE
val divOrFail: Endpoint[Int] = post("div" :: int :: int) { (a: Int, b: Int) => if (b == 0) BadRequest(new ArithmeticException("...")) else Ok(a / b) }
FILTER (FINAGLE)
• Many common behaviours are service agnostic
• Cross cutting concerns
• Timeouts, logging, retries, stats, authentication, etc.
• Filters are composed over services
• Alter the behaviour of a service without caring what it is
FILTER EXAMPLE
val timeout: Filter[...]val auth: Filter[...]val service: Service[Req, Resp]
val composed = timeout andThen auth andThen service
FILTERS ARE FUNCTIONS
type Filter[...] = (ReqIn, Service[ReqOut, RespIn]) => Future[RespOut]
FILTERS ARE TYPESAFE
// A service that requires an authenticated requestval service: Service[AuthReq, Resp]
// Bridge with a filterval auth: Filter[HttpReq, HttpResp, AuthHttpReq, HttpResp]
// Authenticate, and serveval authService: Service[HttpReq, HttpResp] = auth andThen service
FUTURE
• A placeholder for a value that may not yet exist
• Long computations, network calls, disk reads, etc.
• The value is supplied concurrently (executed on thread pool)
• Like callbacks, but not shit
• Oh, and composable (monadic)
CALLBACK FUTURES
val f: Future[String]
f onSuccess { s => log.info(s)} onFailure { ex => log.error(ex)}
STATES OF A FUTURE
• 3 states; empty, complete or failed
• “Taints” the types of calling code
• Easy to program against & make async explicit
• Forces handling of async behaviour
• Can also be blocked (if required)
FUTURE IN PRACTICE
val dbUser = facebook.authenticate(token).flatMap { fbUser => val query = findByEmail(fbUser.email).result database.run(query).flatMap(_.headOption)}dbUser.transform { case Return(user) => success(user) case Throw(e) => handleError(e)}
SERVICE (FINAGLE)
• System boundaries are represented by asynchronous functions called services
• Symmetric and uniform API represents both clients and servers
• You never (usually) write a Finagle service, Finch does that for you
• Services are monadic (you’ll see this a lot…)
SERVICES ARE FUNCTIONS
type Service[Req, Resp] = Req => Future[Resp]
SERVICES IN FINCH
object LiegeApi extends ErrorOps with ResponseEncoders { private def api = usersApi() :+: ridesApi()
def apiService: Service[Request, Response] = { val service = api.handle(errorHandler).toService RequestLoggingFilter.andThen(service) }}
5 GOOD BITS
DATABINDING
Given a model
val ts: Endpoint[Token] = (param("t") :: param("a")).as[Token]val ts: Endpoint[Token] = Endpoint.derive[Token].fromParams
case class Token(token: String, algorithm: String)
Parse the querystring
val getToken: Endpoint[Token] = get("tokens" ? ts) { (t: Token) => ... }
Automatically parse the querystring in an endpoint
DATABINDING
Given a modelcase class Token(token: String, algorithm: String)
{ "token": "CAAX...kfR", "algorithm": "sha1"}
post("sign-in" ? body.as[Token]) { (t: Token) => ... }
And incoming JSON from a POST request
We can bind as
“CONTROLLER”object RidesApi extends HttpOps with Logging { def ridesApi() = list :+: details
def list: Endpoint[List[Attendance]] = get("rides" ? authorise) { u: AuthenticatedUser => ... }
def details: Endpoint[Attendance] = get("rides" / string("type") / string("id") ? authorise) { (backend: String, rideId: Id, u: AuthenticatedUser) => ... }}
class ItemsApiController extends Controller { val itemsService = ... val itemReader = body.as[Item]
def findItemById(itemId: Long): Action = securedAction { reqContext => itemsService.findItemById(itemId) }
def userItems: Action = securedAction(pageReader) { page => implicit reqContext =>
itemsService.userItems(user.id.get, PageRequest(page)) } override def routes: Endpoint[HttpRequest, HttpResponse] = { (Get / "api" / "items" /> userItems) | (Get / "api" / "items" / long /> findItemById) | (Post / "api" / "items" /> newItem) | }}
import io.finch._import ru.arkoit.finchrich.controller._
object MyAwesomeController extends Controller { val healthcheck = get("healthcheck") { Ok() }
val greeter = get("greet" / param("name")) { n: String => Ok(s"Hello, $n!") }}
val ep = controllerToEndpoint(MyAwesomeController)
Source: https://github.com/akozhemiakin/finchrich
METRICS
val stats = Stats(statsReceiver)val server = Http.server.configured(stats).serve(":8081", api)
val rides: Counter = statsReceiver.counter("rides")rides.incr()
val ridesLatency: Stat = statsReceiver.stat("rides_latency")Stat.time(ridesLatency) { rides(u).map(rs => Ok(rs.map(r => Attendance(u, r)))) }
HTTP CLIENTS
val client = Http.client.newService("twitter.com:8081,twitter.com:8082")
val f: Future[HttpRep] = client(HttpReq("/"))
val result: Future[String] = f.map { resp => handleResponse(resp) }
TESTING
service(HttpReq("/")) map { resp => doStuff(resp) }
“ENTITIES”
case class User( id: Option[Int] = None, name: String, email: String,
location: Option[String], avatarUrl: String)
DDL
final class UserOps(tag: Tag) extends Table[User](tag, "users") { def id = column[Int]("id", O.PrimaryKey, O.AutoInc) def name = column[String]("name") def email = column[String]("email") def location = column[String]("location")
...
def nameIdx = index("name_idx", name, unique = true)}
QUERY OPS
object UserOps extends TableQuery(new UserOps(_)) {
...
}
TYPESAFE LOOKUPS
val findByEmail = this.findBy(_.email)val findByName = this.findBy(_.name)
DB ACCESS
def insert(u: User) = UserOps += u
DB ACCESS
def userForToken(token: UserAccessToken): Future[Option[AuthenticatedUser]] = database.run(find(token).result).map(_.headOption.flatMap(asAuthenticatedUser))
def deauthenticateUser(token: AuthToken): Future[Unit] = { val q = for {u <- UserOps if u.authToken === token.asSessionId} yield u.authToken database.run(q.update(null)).flatMap(_ => Future.Done)}
MIGRATIONS
object Database { lazy val migrationDatabase = new MigrationDatabase { def migrate(): Unit = { val flyway = new Flyway() flyway.setDataSource(env.dbUrl, env.dbUsername, env.dbPassword) flyway.migrate() } }}
7 ISSUES
CONSTRAINTS
• Limited support for content type negotiation
• No support for schema first development (Swagger)
• Only supports HTTP, JSON, XML, text, etc. (obviously)
ISSUES
• Twitter stack
• Everything is async, “sensitive to blocking code”, “reactive” bandwagon
• Stuck to netty3
• Documentation not exhaustive, need to rely on Gitter
FUTURES
• Futures are hard to compose
• Representation vs. execution
• Try clouds the issue
FUTURES
• respond vs transform
• respond for purely side-effecting callbacks
• map & flatMap for dealing strictly with successful
computations
• handle and rescue for dealing strictly with exceptional
computations
• No support for orElse
8 GETTING STARTED
WHEN SHOULD I USE IT?
• Complex long / lived system / many developers
• Scale or performance requirements
• Integration with downstream services
• Need to run on the JVM
FINCH
• Watch the Finch videos
• https://skillsmatter.com/skillscasts/6876-finch-your-rest-api-as-a-monad
• Examples
• https://github.com/finagle/finch/tree/master/examples/src/main/scala/io/finch
• Best practices
• https://github.com/finagle/finch/blob/master/docs/best-practices.md
READ UP ON FINAGLE
• Finagle Users Guide
• Your function as a server (original Finagle paper)
• The anatomy of a twitter microservice
• Fault tolerant clients with Finagle
66
QUESTIONS?
7 YOW WEST SLIDES
IDEAS
• More on request reader stuff, auth, etc.
• Using circle’s auto-derivation
• Abstracting/mapping Twitter Futures from/between Scala Futures from Scalaz Tasks
IDEAS
• Other data layers
• Quill, finagle-mysql