Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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)) } }

Slide 14

Slide 14 text

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)) } }

Slide 15

Slide 15 text

Typesafe HTTP Contracts • 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? val miner: HttpHandler = routes( "/mine/{btc}" bind POST to { r: Request -> val newTotal: Int = r.path("btc")!!.toInt() + 1 Response(OK).body("""{"value":$newTotal}""") } )

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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}""") } ) data class BTC(val value: Int) { operator fun plus(that: BTC) = BTC(value + that.value) override fun toString() = value.toString() } • Let’s introduce a domain type to wrap our primitive val btcPath: PathLens = Path.int().map(::BTC).of("btc") val btcBody: BiDiBodyLens = Body.auto().toLens() • … and create lenses to do automatic marshalling:

Slide 18

Slide 18 text

Lens application • 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 val btcPath: PathLens = Path.int().map(::BTC).of("btc") val btcBody: BiDiBodyLens = Body.auto().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)) } ) )

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Standardised Server & Clients 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()) Filter.then(that: Filter) -> Filter By utilising the ability to “stack” Filters, we can build reusable units of behaviour

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

D E M O T I M E

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

CORE ecosystem HamKrest

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Thanks! __________? quickstart: start.http4k.org web: www.http4k.org slack: #http @ kotlinlang @http4k @daviddenton [email protected] @s4nchez [email protected]