$30 off During Our Annual Pro Sale. View Details »

Server as a Function. In Kotlin. _______________

Server as a Function. In Kotlin. _______________

In this talk, you'll learn about how we successfully rewrote the website of a major scientific publisher to pure Kotlin, serving millions of daily requests and in the process created the open source http4k microservice toolkit.

The talk also covers how the team migrated the stack to Continuous Delivery-based deployment into a private on-premise cloud, and how http4k helped us introduce new failure-mode and inter-service contract testing techniques.

This approach combines functional programming concepts and the versatility of Kotlin to produce applications that are simpler and more testable than most Java are accustomed to experience. And the best of it: with absolutely no magic!

David Denton

October 04, 2018
Tweet

More Decks by David Denton

Other Decks in Programming

Transcript

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

    View 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

    View 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

    View 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

    View 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

    View Slide

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

    View Slide

  7. View 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”))

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

    View 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

    View 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

    View 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")
    )

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

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

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

    View 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

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

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

    View Slide

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

    View 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

    View 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

    View 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

    View 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

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

    View Slide

  25. Performance
    •Best performing Kotlin library

    •http4k + Apache server

    •Standard JVM tuning
    Full implementation @ http://bit.ly/techempower

    View 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

    View Slide

  27. D E M O T I M E

    View Slide

  28. View Slide

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

    Auth: http4kbox:http4kbox
    CORE

    View Slide

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

    View 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

    View 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

    View Slide

  33. CORE
    ecosystem
    HamKrest

    View 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

    View Slide

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

    View Slide