Slide 1

Slide 1 text

2017.02.20 JJUG Night Seminor Kotlin Spark Framework with Kotlin @stormcat24

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

新たな基軸言語を求めた

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Spark Framework

Slide 18

Slide 18 text

http://sparkjava.com/

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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") + "!");
 }
 }


Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Kotlin使ってこ fun main(args: Array) {
 
 get("/echo", { req, res ->
 "Hello, ${req.queryParams("name")}!"
 })
 
 }


Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Controller package io.stormcat.controller
 
 import spark.*
 
 class EchoController {
 
 val echo = Route { req, res ->
 "Hello, ${req.queryParams("name")}!"
 }
 }

Slide 25

Slide 25 text

Routing package io.stormcat
 
 import io.stormcat.controller.EchoController
 import spark.Spark.*
 
 fun main(args: Array) {
 
 get("/echo", EchoController().echo)
 
 }


Slide 26

Slide 26 text

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


Slide 27

Slide 27 text

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 ‣ 認証や、横断的関心事の解決

Slide 28

Slide 28 text

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 をサポート

Slide 29

Slide 29 text

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内に気軽に書けて(・∀・)イイ!!

Slide 30

Slide 30 text

拡張関数 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に関数を生やして取れるようにしてしまう ‣ ラップするより拡張という選択

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

愚直な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から取得

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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として設定

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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 


Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Thanks