Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Server as a Function In Kotlin (pt_br)

Server as a Function In Kotlin (pt_br)

Ivan Sanchez

January 11, 2018
Tweet

More Decks by Ivan Sanchez

Other Decks in Programming

Transcript

  1. A plataforma Oscar •Materiais publicados por: •2 anos, dezenas de

    milhões de requests por dia •Serve conteúdo científico
  2. Arquitetura Oscar Router Identity & Entitlements Sites Composer Component Service

    Component Service Component Service Product Article Legacy Journal Sites Site CMS Site CD build & deploy by Deployed to Monorepo in
  3. Techrospective • Modelo HTTP mutável • Configuração de dependências inchada

    • Roteamento por mágica • Cenários end-to-end difíceis de se testar • Programação funcional em Java vs Kotlin Ação: vamos começar de novo, em Kotlin.
  4. BarelyMagical • Projeto parte de um Hackday • Kotlin wrapper

    para Utterlyidle • Usável como biblioteca • Roteamento simplificado • Server as a Function
  5. • Artigo de 2013 de Marius Eriksen @ Twitter Server

    as a Function • 2 conceitos principais: Service e Filter Service<Req, Resp> = (Req) -> Future<Resp> • Sistemas representados por funções assíncronas: Filter<ReqIn, RespIn, ReqOut, RespOut> = (ReqIn, Service<ReqOut, RespOut>) -> Future<RespIn> • Preocupações gerais e transformações de I/O:
  6. SaaF: na prática • Sistema de RPC "protocol agnostic" •

    Usado em produção no Twitter • Scala + Netty • Fintrospect - Camada tipada para Finagle • Boa performance • Sistema de "filtros" • Programação assíncrona estranha • Scala fintrospect.io
  7. Conceito: HttpMessage “Representa a mensagem do padrão HTTP.” val request

    = Request(Method.GET, "http://server/path") .query("name", "value") .header("name", "value") .body("hello world") val response = Response(Status.I_M_A_TEAPOT) .header("name", "value") .body("hello world") http4k representa esses com data classes imutáveis:
  8. Conceito: Service HttpHandler “Passa uma requisição. Retorna uma resposta” HttpHandler:

    (Request) -> Response é apenas uma função: val echo: HttpHandler = { req: Request -> Response(OK).body(req.body) } val resp: Response = echo(Request(POST, “/echo“).body(“hello”))
  9. Conceito: Filter “Permite processar mensagens antes/depois de uma função” Filter:

    (HttpHandler) -> HttpHandler é apenas uma função: val twitterFilter = Filter { next: HttpHandler -> { req: Request -> val tweet: Request = req.body(req.bodyString().take(140)) next(tweet) } } val tweet: HttpHandler = twitterFilter.then(handler)
  10. Conceito: Router Router: (Request) -> HttpHandler? é apenas uma função:

    “Resolve qual HttpHandler pode lidar com uma requisição” val routes: HttpHandler = routes( "/echo" bind POST to echo, "/twitter" bind routes( “/tweet” bind POST to tweet ) ) Routers podem ser combinados e gerar novos HttpHandlers Busca em árvore simples, retorna 404 se não encontrar nada
  11. Testando HttpHandlers Com funcões simples e data classes, testar é

    trivial 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)) } }
  12. Servindo funções val echo: HttpHandler = { r: Request ->

    Response(OK).body(r.body) } val server: Http4kServer = echo.asServer(Jetty(8000)).start() Servidor criado a partir de um HttpHandler
  13. Cliente HTTP “Passa uma requisição. Retorna uma resposta” HttpHandler: (Request)

    -> Response usa a mesma função, HttpHandler! val client: HttpHandler = ApacheClient() val response: Response = client( Request(GET, "https://www.http4k.org/search") .query("term", “http4k is cool") )
  14. Contratos tipados • Como introduzir tipos em cima de uma

    requisição? • Locais: Path/Query/Header/Body/Form • Mandatoriedade • Conversões + Typesafety • Como criar respostas a partir de tipos bem definidos? val miner: HttpHandler = routes( “/mine/{btc}" bind POST to { r: Request -> val newTotal: Int = r.path("btc")!!.toInt() + 1 val out = “You have $newTotal BTC" val json = """{ "value": "$out"}""" Response(OK).body(json) } )
  15. “Lenses and their cousins are a way of declaring how

    to focus deeply into a complex structure. They form a combinator library with sensible, general behavior for things like composition, failure, multiplicity, transformation, and representation.” Conceito: Lens
  16. Calma lá… “Lens é uma maneira de “extrair" ou “injetar"

    valores em objetos complexos” Extract: (HttpMessage) -> X Inject: (X, HttpMessage) -> HttpMessage é apenas uma função. Ok, duas.
  17. Lens via exemplo 1/3 val btcPath = Path.int().of("btc") val miner:

    HttpHandler = CatchLensFailure.then( routes( “/mine/{btc}” bind POST to { r: Request -> val newTotal: Int = btcPath.extract(r) + 1 val out = “You have $newTotal BTC" val json = """{ "value": "$out"}""" Response(OK).body(json) } )) • http4k possui Lens para mensagens HTTP. • Violações geram HTTP 400 (Bad Request).
  18. Lens via exemplo 2/3 • Podemos usar “map()” para usar

    novos tipos: data class BTC(val value: Int) { operator fun plus(that: BTC) = BTC(value + that.value) fun toString(): value.toString() } val btcPath = Path.int().map(::BTC).of("btc") val miner: HttpHandler = CatchLensFailure.then( routes( “/mine/{btc}” bind POST to { r: Request -> val newTotal: BTC = btcPath.extract(r) + BTC(1) val message = “You have $newTotal BTC” val json = """{ "value": "$message"}""" Response(OK).body(json) } ))
  19. Lens via exemplo 3/3 • Também pode ser usada para

    serializar respostas: data class BTC(val value: Int) { operator fun plus(that: BTC) = BTC(value + that.value) fun toString(): value.toString() } class MinedCoin(btc: Btc) { val value = “You have ${btc + BTC(1)} BTC” } val btcPath = Path.int().map(::BTC).of("btc") val jsonBody = Body.auto<MinedCoin>().toLens() val miner: HttpHandler = CatchLensFailure.then( routes( “/mine/{btc}” bind GET to { r: Request -> val mined = MinedCoin(btcPath.extract(r)) jsonBody.inject(mined, Response(OK)) } ))
  20. Business Abstraction Remote Client Launcher Load Environment Configuration Embedded Server

    Launch Application Stack Logging Metrics Remote Clients Application Business Abstraction Business Abstraction As camadas do bolo Route Route Route Route
  21. Servidor e cliente padrão 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 Afinal, Filters podem ser combinados e reutilizados.
  22. Fake Dependencies! Fake HTTP Dependency State • Tira vantagem de

    Body lenses • Comportamento baseado em estados simples • Podem rodar em memória ou num servidor real • Erros são fáceis de simular
  23. 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)
  24. Exemplo de CDC class Chamber(private val client: HttpHandler) { fun

    echo(input: String) = client( Request(POST, “http://server.com/echo").body(input)).bodyString() } abstract class EchoChamberCdc { abstract val client: HttpHandler @Test fun `echoes message`() { assertThat(Chamber(client).echo("hello"), equalTo("hello")) } } class RealEchoChamberTest : EchoChamberCdc() { override val client: HttpHandler = OkHttp() } class FakeEchoChamberTest : EchoChamberCdc() { override val client: HttpHandler = { r: Request -> Response(Status.OK).body(r.body) }
  25. Test Environment Configuration Testando Sistemas • É possível combinar essas

    aplicações e construir um ambiente único em memória • Podemos simular falhas em qualquer parte do sistema, injetando um novo Fake que produz erros particulares. • Todas as aplicações internas e externas são HttpHandlers
  26. A plataforma Oscar agora •Serviços 100% Kotlin •Debugar ficou fácil,

    sem mágica •Menos boilerplate •Builds super rápidos porque muito mais pode ser feito em memória •Testar end-to-end é fácil •Código fácil de se navegar
  27. serverless4k? package org.http4k.example import org.http4k.aws.lambda.AppLoader import org.http4k.core.HttpHandler import org.http4k.core.Request import

    org.http4k.core.Response import org.http4k.core.Status.Companion.OK object TweetEcho : AppLoader { override fun invoke(env: Map<String, String>): HttpHandler = { req: Request -> Response(OK).body(req.bodyString().take(140)) } } Proguard reduz esse Lambda JAR para <400kb Watch this space!