Slide 1

Slide 1 text

Server as a Function. In Kotlin. _________________. Ivan Sanchez & David Denton

Slide 2

Slide 2 text

A plataforma Oscar •Materiais publicados por: •2 anos, dezenas de milhões de requests por dia •Serve conteúdo científico

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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.

Slide 5

Slide 5 text

BarelyMagical • Projeto parte de um Hackday • Kotlin wrapper para Utterlyidle • Usável como biblioteca • Roteamento simplificado • Server as a Function

Slide 6

Slide 6 text

• Artigo de 2013 de Marius Eriksen @ Twitter Server as a Function • 2 conceitos principais: Service e Filter Service = (Req) -> Future • Sistemas representados por funções assíncronas: Filter = (ReqIn, Service) -> Future • Preocupações gerais e transformações de I/O:

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Server as a Function. In Kotlin. ______ . Delivered

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

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:

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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)

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

“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

Slide 19

Slide 19 text

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.

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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.

Slide 26

Slide 26 text

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

Slide 27

Slide 27 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 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

outros módulos http4k HamKrest

Slide 32

Slide 32 text

Server as a Function. In Kotlin. ________________? Sem um servidor

Slide 33

Slide 33 text

Server as a Lambda AWS API Gateway AWS Lambda

Slide 34

Slide 34 text

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): HttpHandler = { req: Request -> Response(OK).body(req.bodyString().take(140)) } } Proguard reduz esse Lambda JAR para <400kb Watch this space!

Slide 35

Slide 35 text

Obrigado! __________? quickstart: start.http4k.org web: www.http4k.org slack: #http4k no Kotlinlang @http4k @s4nchez [email protected] @daviddenton [email protected]