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. stormcat24 ‣ CyberAgent, Inc. ‣ FRESH! ‣ DevOps / Docker

    Comedian ‣ ワタシコトリンチョットカケルアルヨ ‣ 最近の興味はRust
  2. technology stack ‣ Microservices Architecture ‣ Full Dockernized ‣ Amazon

    Web Services ‣ EC2 Container Services(ECS) ‣ Golang
  3. Golang と FRESH ‣ 2016/01のリリースから一貫してGolangを主要言語として採用 ‣ 1Service = 1Container =

    1Repository ‣ Public/Internal問わず十数個のMicroservicesが誕生 ‣ シンプル
  4. しかしつらくなってきた ‣ 基本的に筋力に頼る言語 ‣ 記述量が多い ‣ 高階関数ほしい ‣ 生産性を上げるためのコード生成 ‣

    タイプ量 成果と満足感❓ ‣ ゴリゴリとAPIを書いていくのはしんどいというメンバーの総意
  5. Spark Framework is ‣ Java8ベースのマイクロWebフレームワーク ‣ Apache Sparkとは別物 ‣ ググラビリティ・・・

    ‣ Lambda式を利用 ‣ Imspired by Sinatra ‣ Routing/Filter等最低限の機能、DIとか無いです
  6. 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") + "!");
 }
 }

  7. Spark Framework with Kotlin ‣ Java8 Lambda式だけでは満足できない ‣ Spark Frameworkのシンプルさと、Kotlinの良さを活かす

    ‣ 必要なライブラリを追加して、自力で統合していくスタイル ‣ SpringのようにIntegrationが充実しているわけではないが、相 性の悪さだったり、迷いはあまり無い
  8. Kotlin使ってこ fun main(args: Array<String>) {
 
 get("/echo", { req, res

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

  9. Spark with Kotlinお品書き ‣ Controller / Routing ‣ Filters ‣

    ResponseTransformer(JSON) ‣ data class ‣ 拡張関数 ‣ Guice(Dependency Injection) ‣ 愚直なDI ‣ okhttp ‣ hystrix ‣ Metrics(Jolokia)
  10. Controller package io.stormcat.controller
 
 import spark.*
 
 class EchoController {


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

    main(args: Array<String>) {
 
 get("/echo", EchoController().echo)
 
 }

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

  13. Filters package io.stormcat
 
 import io.stormcat.controller.EchoController
 import io.stormcat.filter.ResponseHeaderFilter
 import spark.Spark.*


    
 fun main(args: Array<String>) {
 
 // filters
 after(ResponseHeaderFilter())
 
 // routing
 get("/echo", EchoController().echo)
 
 }
 ‣ before/after ‣ 認証や、横断的関心事の解決
  14. 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 をサポート
  15. 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内に気軽に書けて(・∀・)イイ!!
  16. 拡張関数 package io.stormcat.controller
 
 import spark.Request
 
 fun Request.authUser(): User

    {
 val user = this.attribute<User>("authUser")
 return user ?: throw RuntimeException("Authorization Required")
 } ‣ spark.Requestに認証情報をセット ‣ spark.Requestに関数を生やして取れるようにしてしまう ‣ ラップするより拡張という選択
  17. 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
  18. 愚直なDI fun main(args: Array<String>) {
 
 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から取得
  19. okhttp class UserApiClient @Inject constructor(val mapper: ObjectMapper, val client: OkHttpClient,


    @Named("apiDomain") val apiDomain: String
 ) {
 
 fun getUser(ids: List<Long>): List<User> {
 
 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<List<User>>() {}
 return mapper.readValue<List<User>>(raw, tr)
 }
 } ‣ Microservices間の通信 ‣ data classにdeserialize
  20. Hystrix interface GetUserCommandFactory {
 fun create(@Assisted("id") ids: List<Long>): GetUserCommand
 }


    
 interface GetUserCommand {
 fun execute(): List<User>
 }
 
 class GetUserCommandImpl @Inject constructor(
 @Assisted("ids") val ids: List<Long>,
 val userApiClient: UserApiClient,
 val getUserCommandKey: HystrixCommandGroupKey
 ) : HystrixCommand<List<User>>(getUserCommandKey), GetUserCommand {
 
 override fun run(): List<User> {
 return userApiClient.getUser(ids)
 }
 }
  21. 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として設定
  22. 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