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

server as a function in kotlin
SMART_READER_LITE
LIVE PREVIEW

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


slide-1
SLIDE 1

Server as a Function. In Kotlin. _________________.

David Denton & Ivan Sanchez KotlinConf - October 4th 2018

slide-2
SLIDE 2

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

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

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

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

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

slide-7
SLIDE 7
slide-8
SLIDE 8

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

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

(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

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

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

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

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

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

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

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

  • perator fun plus(that: BTC) = BTC(value + that.value)
  • verride 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:
slide-18
SLIDE 18

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

slide-19
SLIDE 19

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

slide-20
SLIDE 20

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

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

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

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

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

Performance

  • Best performing Kotlin library
  • http4k + Apache server
  • Standard JVM tuning

Full implementation @ http://bit.ly/techempower

slide-26
SLIDE 26

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

D E M O T I M E

slide-28
SLIDE 28
slide-29
SLIDE 29

… all this in 70 lines of Kotlin!

http://bit.ly/http4kbox https://http4kbox.http4k.org Auth: http4kbox:http4kbox

CORE

slide-30
SLIDE 30

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

slide-31
SLIDE 31

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

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

CORE

ecosystem

HamKrest

slide-34
SLIDE 34

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

Thanks! __________?

quickstart: start.http4k.org web: www.http4k.org slack: #http @ kotlinlang @http4k @daviddenton david@http4k.org @s4nchez ivan@http4k.org