server as a function in kotlin
play

Server as a Function. In Kotlin. _________________. KotlinConf - - PowerPoint PPT Presentation

Server as a Function. In Kotlin. _________________. KotlinConf - October 4th 2018 David Denton & Ivan Sanchez The Oscar Platform Strategic journal delivery platform for a global academic publisher Top 1000 site globally, delivers


  1. Server as a Function. In Kotlin. _________________. KotlinConf - October 4th 2018 David Denton & Ivan Sanchez

  2. The Oscar Platform • Strategic journal delivery platform for a global academic publisher • Top 1000 site globally, delivers ~10s of millions Req/day Monorepo in CD build & deploy by Deployed to

  3. Techrospective Issues: • Mutable HTTP model • Boilerplate around application setup is bloated • Magic around routing proving hard to debug • Hard to test end-to-end scenarios • Functional Java vs native Kotlin woes Action: Let’s try something in pure Kotlin

  4. BarelyMagical • Hackday project • ~40 line Kotlin wrapper for Utterlyidle • Use it as a library • Simple routing • Server as a function See it @ http://bit.ly/BarelyMagical

  5. Server as a Function • 2013 white paper from Marius Eriksen @ Twitter • Defined the composing of Services using just 2 types of asynchronous function: • Service - Represents a system boundary (symmetric) • Filter - aka middleware - Models application agnostic concerns and I/O transforms • Twitter implementation is Scala-based Finagle library • Protocol agnostic == too generic • Future-based == adds complexity

  6. Server as a Function. In Kotlin. ______ _. Distilled

  7. Concept: Service HttpHandler “It turns an HTTP Request into an HTTP Response” ie. it’s a function! HttpHandler: (Request) -> Response val echo: HttpHandler = { req: Request -> Response( OK ).body(req.body) } val resp: Response = echo(Request(POST, “/ echo“).body(“hello”))

  8. Concept: Filter “Provides pre and post processing on an HTTP operation” ie. it’s a function! Filter: (HttpHandler) -> HttpHandler val twitterFilter = Filter { next: HttpHandler -> { req: Request -> val tweet: Request = req.body(req.bodyString(). take (140)) next(tweet) } } val tweet: HttpHandler = twitterFilter. then (echo)

  9. (New) Concept: Router “Matches an HttpHandler against a Request” ie. it’s a function! Router: (Request) -> HttpHandler? Can compose multiple routers to make an HttpHandler val routes: HttpHandler = routes ( "/echo" bind POST to echo, "/twitter" bind routes ( “/tweet” bind POST to tweet ) ) http4k does a depth-first search on the tree, then falls back to 404

  10. Serving HTTP We can attach an HttpHandler to a running container val echo : HttpHandler = { r: Request -> Response( OK ).body(r.body) } val server : Http4kServer = echo . asServer (Undertow(8000)).start()

  11. Consuming HTTP Can reuse the symmetric HttpHandler API: HttpHandler: (Request) -> Response val client: HttpHandler = ApacheClient() val response: Response = client( Request(GET, "https://www.http4k.org/search") .query("term", “http4k is cool") )

  12. Extreme Testability Testing http4k apps is trivial because: • the building blocks are just functions • messages are immutable data classes! val echo: HttpHandler = { r: Request -> Response( OK ).body(r.body) } class EchoTest { @Test fun `handler echoes input`() { val input: Request = Request(POST, "/echo").body("hello") val expected: Response = Response( OK ).body("hello") assertThat (echo(input), equalTo (expected)) } }

  13. Extreme Testability Testing http4k apps is trivial because: • the building blocks are just functions • messages are immutable data classes! val echo: HttpHandler = SetHostFrom(Uri.of("http://myserver:80")) . then (ApacheClient()) class EchoTest { @Test fun `handler echoes input`() { val input: Request = Request(POST, "/echo").body("hello") val expected: Response = Response( OK ).body("hello") assertThat (echo(input), equalTo (expected)) } }

  14. Typesafe HTTP Contracts val miner: HttpHandler = routes ( "/mine/{btc}" bind POST to { r: Request -> val newTotal: Int = r. path ("btc")!!. toInt () + 1 Response( OK ).body("""{"value":$newTotal}""") } ) • How do we enforce our incoming HTTP contract? • Locations: Path/Query/Header/Body/Form • Optionality - required or optional? • Marshalling + Typesafety • What about creating outbound messages?

  15. Concept: Lens “A Lens targets a specific part of a complex object to either GET or SET a value” ie. it’s a function - or more precisely 2 functions! Extract: (HttpMessage) -> X Inject: (X, HttpMessage) -> HttpMessage • these functions exist on the lens object as overloaded invoke() functions

  16. Lens example • Revisiting the earlier example… val miner: HttpHandler = routes ( "/mine/{btc}" bind POST to { r: Request -> val newTotal: Int = r. path ("btc")!!. toInt () + 1 Response( OK ).body("""{"value":$newTotal}""") } ) • Let’s introduce a domain type to wrap our primitive data class BTC(val value: Int) { operator fun plus(that: BTC) = BTC(value + that.value) override fun toString() = value.toString() } • … and create lenses to do automatic marshalling: val btcPath: PathLens<BTC> = Path. int ().map(::BTC).of("btc") val btcBody: BiDiBodyLens<BTC> = Body. auto <BTC>().toLens()

  17. Lens application val btcPath: PathLens<BTC> = Path. int ().map(::BTC).of("btc") val btcBody: BiDiBodyLens<BTC> = Body. auto <BTC>().toLens() val miner: HttpHandler = CatchLensFailure. then ( routes ( "/mine/{btc}" bind POST to { r: Request -> val newTotal: BTC = btcPath(r) + BTC(1) btcBody(newTotal, Response( OK )) } ) ) • http4k provides Lenses targeting all parts of the HttpMessage • Via a CatchLensFailure filter, contract violations automatically produce a BadRequest (400) • Auto-marshalling JSON support for Jackson, GSON and Moshi

  18. Server as a Function. In Kotlin. _______________. Dogfood Edition

  19. The Layer Cake Launcher Application Stack Business Abstraction Load Logging Application Environment Remote Client Configuration Business Route Abstraction Metrics Route Route Business Embedded Remote Abstraction Server Route Clients Launch

  20. Standardised Server & Clients By utilising the ability to “stack” Filters, we can build reusable units of behaviour Filter.then(that: Filter) -> Filter fun serverStack(systemName: String, app: HttpHandler): HttpHandler = logTransactionFilter(“IN”, systemName) . then (recordMetricsFilter(systemName)) . then (handleErrorsFilter()) . then (app) fun clientStack(systemName: String): HttpHandler = logTransactionFilter(“OUT”, systemName) . then (recordMetricsFilter(systemName)) . then (handleErrorsFilter()) . then (ApacheClient())

  21. Fake Your Dependencies! • Leverage Body lenses Fake HTTP Service • Simple state-based behaviour State • Run in memory or as server • Easy to simulate failures

  22. Application Testing Test • All internal and external applications and clients are HttpHandlers Environment Configuration • We can simply inject apps into each other in order to build an o ffl ine environment • Using fakes, we can inject failure to test particular scenarios

  23. Consumer Driven Contracts Abstract Test Contract Business Abstraction Success scenarios Fake Dependency Test Real Dependency Test Fake Failure System Remote scenarios (HttpHandler) Environment Client Configuration (HttpHandler) State

  24. Performance • Best performing Kotlin library • http4k + Apache server • Standard JVM tuning Full implementation @ http://bit.ly/techempower

  25. What did we gain? • Pure Kotlin Services • No Magic == easy debugging • Boilerplate reduction • In-Memory == super quick build • End-to-End testing is easy

  26. D E M O T I M E

  27. … all this in 70 lines of Kotlin! CORE https://http4kbox.http4k.org Auth: http4kbox:http4kbox http://bit.ly/http4kbox

  28. Server as a Function. In Kotlin. _________________. Without the Server.

  29. serverless4k • Http4k Apps can run as Lambdas by implementing a single interface AWS API Gateway AWS Lambda • Applying Proguard shrinks binary size to 100’s of Kb • Dependencies can have a significant e ff ect on cold start time** ** http://bit.ly/coldstartwar

  30. native4k • GraalVM is a universal VM • Can compile JVM apps into native binaries • Small size + quick startup • BUT: No simple reflection = hard for many libs • http4k apps with Apache-backend work out of the box! • Simple Graal http4k app = 6mb • With Docker (Alpine), 9mb

  31. ecosystem HamKrest CORE

  32. http4k nanoservices “5 useful mini-apps which all fit in a tweet!” static (Directory() { ws: Websocket -> . asServer (SunHttp()) while (true) { .start() ws.send(WsMessage( Instant.now().toString()) ProxyHost(Https) ) . then (JavaHttpClient()) Thread.sleep(1000) . withChaosControls (Latency() } . appliedWhen (Always)) } . asServer (Jetty()).start() . asServer (SunHttp()) .start() JavaHttpClient(). let { client -> Disk("store").requests() ProxyHost(Https) . forEach { . then (RecordTo(Disk("store"))) println ( it ) . then (JavaHttpClient()) client( it ) . asServer (SunHttp()) } .start() } http://bit.ly/http4knanoservices

  33. @daviddenton @s4nchez david@http4k.org ivan@http4k.org Thanks! __________? @http4k quickstart: start.http4k.org web: www.http4k.org slack: #http @ kotlinlang

Recommend


More recommend