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

Spark Framework with Kotlin

stormcat24
February 20, 2017

Spark Framework with Kotlin

2017.02.20 JJUG Night Seminor Kotlin

stormcat24

February 20, 2017
Tweet

More Decks by stormcat24

Other Decks in Programming

Transcript

  1. 2017.02.20 JJUG Night Seminor Kotlin
    Spark Framework
    with Kotlin
    @stormcat24

    View full-size slide

  2. stormcat24
    ‣ CyberAgent, Inc.
    ‣ FRESH!
    ‣ DevOps / Docker Comedian
    ‣ ワタシコトリンチョットカケルアルヨ
    ‣ 最近の興味はRust

    View full-size slide

  3. technology stack
    ‣ Microservices Architecture
    ‣ Full Dockernized
    ‣ Amazon Web Services
    ‣ EC2 Container Services(ECS)
    ‣ Golang

    View full-size slide

  4. https://speakerdeck.com/stormcat24/docker-darakefalse-fresh-nadong-hua-pei-xin-puratutohuomu

    View full-size slide

  5. https://aws.amazon.com/jp/solutions/case-studies/cyberagent/

    View full-size slide

  6. Golang と FRESH
    ‣ 2016/01のリリースから一貫してGolangを主要言語として採用
    ‣ 1Service = 1Container = 1Repository
    ‣ Public/Internal問わず十数個のMicroservicesが誕生
    ‣ シンプル

    View full-size slide

  7. しかしつらくなってきた
    ‣ 基本的に筋力に頼る言語
    ‣ 記述量が多い
    ‣ 高階関数ほしい
    ‣ 生産性を上げるためのコード生成
    ‣ タイプ量 成果と満足感❓
    ‣ ゴリゴリとAPIを書いていくのはしんどいというメンバーの総意

    View full-size slide

  8. 新たな基軸言語を求めた

    View full-size slide

  9. Microservicesの特性を活かす
    ‣ 言語はServiceによって変えられる
    ‣ 将来的に変えることも想定していた
    ‣ Serviceの特性によって、適材適所なものを選んでいくのが重要
    ‣ ミドル層、低レイヤー層は引き続きGolang
    ‣ 新たに、一つの基軸言語を選ぶ(乱立は(・A・)イクナイ!!)

    View full-size slide

  10. 基軸言語にKotlinを選択
    ‣ API実装の基軸言語として、新たにKotlinを採用
    ‣ 新しいMicroservicesは基本Kotlinで
    ‣ Golangの既存Serviceを置き換えるわけではない

    View full-size slide

  11. Kotlinの理由
    ‣ モダンな文法
    ‣ IDE(IntelliJ IDEA)
    ‣ 一番現実的だった
    ‣ 社内リソース(JVM系人材、Androidでの実績)

    View full-size slide

  12. 助走期間
    ‣ 正式に選定する前に、内部の決済用ServiceをKotlinで実装
    ‣ Spring Boot
    ‣ Springfox
    ‣ AnnotationからSwaggerを吐き出すやつ
    ‣ domaframework
    ‣ すごい

    View full-size slide

  13. 感想
    ‣ 高い生産性
    ‣ そこそこの学習コスト(Java/Spring経験による)
    ‣ もうちょっと薄いものでいいかもしれない?
    ‣ MonolithicなServiceを作るわけではないため

    View full-size slide

  14. Spark Framework

    View full-size slide

  15. http://sparkjava.com/

    View full-size slide

  16. Spark Framework is
    ‣ Java8ベースのマイクロWebフレームワーク
    ‣ Apache Sparkとは別物
    ‣ ググラビリティ・・・
    ‣ Lambda式を利用
    ‣ Imspired by Sinatra
    ‣ Routing/Filter等最低限の機能、DIとか無いです

    View full-size slide

  17. In Java
    package io.stormcat;


    import static spark.Spark.*;


    public class Server {


    public static void main(String[] args) {

    get("/echo", (req, res) -> "Hello, " + req.queryParams("name") + "!");

    }

    }


    View full-size slide

  18. Spark Framework with Kotlin
    ‣ Java8 Lambda式だけでは満足できない
    ‣ Spark Frameworkのシンプルさと、Kotlinの良さを活かす
    ‣ 必要なライブラリを追加して、自力で統合していくスタイル
    ‣ SpringのようにIntegrationが充実しているわけではないが、相
    性の悪さだったり、迷いはあまり無い

    View full-size slide

  19. Kotlin使ってこ
    fun main(args: Array) {


    get("/echo", { req, res ->

    "Hello, ${req.queryParams("name")}!"

    })


    }


    View full-size slide

  20. Spark with Kotlinお品書き
    ‣ Controller / Routing
    ‣ Filters
    ‣ ResponseTransformer(JSON)
    ‣ data class
    ‣ 拡張関数
    ‣ Guice(Dependency Injection)
    ‣ 愚直なDI
    ‣ okhttp
    ‣ hystrix
    ‣ Metrics(Jolokia)

    View full-size slide

  21. Controller
    package io.stormcat.controller


    import spark.*


    class EchoController {


    val echo = Route { req, res ->

    "Hello, ${req.queryParams("name")}!"

    }

    }

    View full-size slide

  22. Routing
    package io.stormcat


    import io.stormcat.controller.EchoController

    import spark.Spark.*


    fun main(args: Array) {


    get("/echo", EchoController().echo)


    }


    View full-size slide

  23. Filters
    package io.stormcat.filter


    import spark.Filter

    import spark.Request

    import spark.Response


    class ResponseHeaderFilter : Filter {


    override fun handle(request: Request, response: Response) {

    response.header("Server", "Your Kotlin Server")

    }

    }


    View full-size slide

  24. Filters
    package io.stormcat


    import io.stormcat.controller.EchoController

    import io.stormcat.filter.ResponseHeaderFilter

    import spark.Spark.*


    fun main(args: Array) {


    // filters

    after(ResponseHeaderFilter())


    // routing

    get("/echo", EchoController().echo)


    }

    ‣ before/after
    ‣ 認証や、横断的関心事の解決

    View full-size slide

  25. ResponseTransformer
    package io.stormcat


    import com.fasterxml.jackson.databind.ObjectMapper

    import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule

    import com.fasterxml.jackson.module.kotlin.KotlinModule

    import spark.ResponseTransformer


    class JsonTransformer : ResponseTransformer {

    val mapper = ObjectMapper()

    .registerModule(JavaTimeModule())

    .registerModule(KotlinModule())


    override fun render(model: Any?): String {

    return mapper.writeValueAsString(model)

    }

    }
    ‣ JacksonのKotlinModule
    ‣ data class をサポート

    View full-size slide

  26. data class
    package io.stormcat.controller


    import spark.*


    class EchoController {


    data class EchoResult(

    val name: String,

    val message: String

    )


    val echo = Route { req, res ->

    EchoResult(

    name = req.queryParams("name"),

    message = "Hello"

    )

    }

    }
    ‣ レスポンスをdata classで扱う
    ‣ Controller内に気軽に書けて(・∀・)イイ!!

    View full-size slide

  27. 拡張関数
    package io.stormcat.controller


    import spark.Request


    fun Request.authUser(): User {

    val user = this.attribute("authUser")

    return user ?: throw RuntimeException("Authorization Required")

    }
    ‣ spark.Requestに認証情報をセット
    ‣ spark.Requestに関数を生やして取れるようにしてしまう
    ‣ ラップするより拡張という選択

    View full-size slide

  28. Guice(Dependency Injection)
    package io.stormcat.controller


    import com.google.inject.Inject

    import io.stormcat.service.UserService

    import spark.*


    class UserController @Inject constructor(

    val userService: UserService

    ) {


    val getUser = Route { req, res ->

    val userId = req.params("id")?.toLong() ?: throw RuntimeException("id is required")

    userService.getUser(userId)

    }

    }
    ‣ Guiceを使ってconstructor injection

    View full-size slide

  29. 愚直なDI
    fun main(args: Array) {


    val injector = Guice.createInjector(object : AbstractModule() {

    override fun configure() {

    bindConstant().annotatedWith(

    Names.named("apiDomain")).to("api.yourexample.com")

    bind(ObjectMapper::class.java)

    .toProvider(ObjectMapperProvider::class.java)

    .`in`(Singleton::class.java)

    }

    })


    val userController = injector.getInstance(UserController::class.java)


    // routing

    get("/user/:id", userController.getUser)


    }
    ‣ 依存関係、スコープを愚直に定義
    ‣ ControllerをInjectorから取得

    View full-size slide

  30. okhttp
    class UserApiClient @Inject constructor(val mapper: ObjectMapper, val client: OkHttpClient,

    @Named("apiDomain") val apiDomain: String

    ) {


    fun getUser(ids: List): List {


    val urlBuilder = HttpUrl.Builder().scheme(“https").host(apiDomain)
    .addPathSegment("users").addPathSegment(ids.joinToString(","))


    val request = Request.Builder().url(urlBuilder.build()).build()


    val response = client.newCall(request).execute()

    if (response.code() != 200) {

    throw RuntimeException("api error")

    }


    val raw = response.body().string()

    val tr = object : TypeReference>() {}

    return mapper.readValue>(raw, tr)

    }

    }
    ‣ Microservices間の通信
    ‣ data classにdeserialize

    View full-size slide

  31. Hystrix
    ‣ github.com/Netflix/Hystrix
    ‣ 分散システムにおいて、回復力のあるアーキテクチャを実現するた
    めのライブラリ
    ‣ Circuit Brakerをサポート

    View full-size slide

  32. Circuit Braker
    ‣ どこか一つのServiceがダウンした際に、リクエストをブロックする
    して依存サービスが連鎖的に影響を受ける 
    ‣ エラー率が閾値を超えた際に、自動でアクセスを遮断するための仕
    組み

    View full-size slide

  33. Hystrix Dashboard
    https://github.com/Netflix/Hystrix/wiki/Dashboard

    View full-size slide

  34. HystrixCommand
    ‣ 外部サービスへのリクエストや、Latencyを注視しておきたい処理
    をHystrixCommandとして実装する
    ‣ HTTP Client実装を内包するような実装が多い
    ‣ 対象処理を容易にFutureやObservable化することができる

    View full-size slide

  35. Hystrix
    interface GetUserCommandFactory {

    fun create(@Assisted("id") ids: List): GetUserCommand

    }


    interface GetUserCommand {

    fun execute(): List

    }


    class GetUserCommandImpl @Inject constructor(

    @Assisted("ids") val ids: List,

    val userApiClient: UserApiClient,

    val getUserCommandKey: HystrixCommandGroupKey

    ) : HystrixCommand>(getUserCommandKey), GetUserCommand {


    override fun run(): List {

    return userApiClient.getUser(ids)

    }

    }

    View full-size slide

  36. Circuit Braker
    val circuitBreakerProperties = HystrixCommandProperties.Setter()

    .withCircuitBreakerEnabled(true)

    .withCircuitBreakerErrorThresholdPercentage(50)


    val getUserCommandKey = HystrixCommand.Setter

    .withGroupKey(HystrixCommandGroupKey.Factory.asKey("getUser"))

    .andCommandPropertiesDefaults(circuitBreakerProperties)
    ‣ Circuit Brakerの閾値を設定できる
    ‣ HystrixCommand初期化時にCommandKeyとして設定

    View full-size slide

  37. Metrics(Jolokia)
    ‣ Jolikiaを入れておけばJVMのMetricsが取れる
    ‣ Dockerを使う場合、Dockerビルドの際に入れておいて、HTTPで取
    れるようにしとくのが楽

    View full-size slide

  38. Metrics(Jolokia)
    FROM java:openjdk-8-jdk-alpine


    COPY . /spark-kotlin


    RUN apk update && \ 

    apk add --virtual build-dependencies build-base bash curl && \

    cd /spark-kotlin && ./gradlew clean && \

    cd /spark-kotlin && ./gradlew build && \

    mkdir -p /usr/local/spark-kotlin/lib && \

    cp -R /spark-kotlin/build/libs/* /usr/local/spark-kotlin/lib/ && \

    curl -o /usr/local/spark-kotlin/lib/jolokia-jvm-agent.jar \

    https://repo1.maven.org/maven2/org/jolokia/jolokia-jvm/1.3.5/jolokia-jvm-1.3.5-agent.jar && \

    apk del build-dependencies && \

    rm -rf /var/cache/apk/* && \

    rm -rf ~/.gradle && \

    rm -rf /spark-kotlin


    ENTRYPOINT java $JAVA_OPTS \

    -javaagent:/usr/local/spark-kotlin/lib/jolokia-jvm-agent.jar=port=8778,host=0.0.0.0 \

    -jar /usr/local/spark-kotlin/lib/spark-kotlin.jar


    EXPOSE 4567 8778 


    View full-size slide

  39. FRESHにおけるSparkとKotlin
    ‣ ユーザー・配信主用の公開APIとして本番稼働開始
    ‣ 各種MicroservicesへのGatewayとしての役割を担う
    ‣ Hystrix Dashboardこれから活用していきたい

    View full-size slide

  40. まとめ
    ‣ Spark + Kotlin十分運用していけるし、現実的な選択肢になった
    ‣ Microservicesトレンドの中、シンプルなものが求められている
    ‣ Server Side Kotlinの波は少しずつ来ている
    ‣ Kotlin気持ち(・∀・)イイ!!

    View full-size slide