In this talk, we show how to combine functional programming concepts and the versatility of Kotlin to produce applications that are simpler and more testable. And the best of it: with absolutely no magic!
Eriksen @ Twitter • Composing of Services using just 2 types of asynchronous function: • Service - Represents a system boundary (symmetric) • Filter - aka middleware - Models application agnostic concerns
“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- fi rst search on the tree, then falls back to 404
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)) } }
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)) } }
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
= 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<BTC> = Path.int().map(::BTC).of("btc") val btcBody: BiDiBodyLens<BTC> = Body.auto<BTC>().toLens() • … and create lenses to do automatic marshalling:
val miner: HttpHandler = CatchLensFailure.then( routes( "/mine/{btc}" bind POST to { r: Request -> val newTotal: BTC = btcPath(r) + BTC(1) btcBody(newTotal, Response(OK)) } ) ) Lens example: before & after val miner: HttpHandler = routes( "/mine/{btc}" bind POST to { r: Request -> val newTotal: Int = r.path("btc")!!.toInt() + 1 Response(OK).body("""{"value":$newTotal}""") } )
= 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
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 • All internal and external applications and clients are HttpHandlers
can inject bad runtime behaviour to simulate failures • Add pre-canned bad-behaviour using the ChaosEngine val chaosStage: Stage = Latency().appliedWhen(Always()) val app: HttpHandler = { req: Request -> Response(OK) } app.withChaosEngine(chaosStage).asServer(Undertow(8000)).start() … or add it to a running service and con fi gure it remotely using the ChaosEngine OpenAPI interface!
into native binaries •Small size + quick startup •BUT: No simple re fl ection = hard for many libs •http4k apps with Apache-backend work out of the box! •Simple Graal http4k app = 6mb •With Docker (Alpine), 9mb
570 releases, 6k commits, ~100 contributors • 66 modules, supporting 17 server/serverless/native backends • Core module is still only 1mb (with zero dependencies) • One of the top Kotlin frameworks in TechEmpower benchmarks • Featured in JetBrains “Serverside Kotlin” Webinar series* *https://www.youtube.com/watch?v=NjoCjupV8HE