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

Spark Framework with Kotlin

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on. →
stormcat24
February 20, 2017

Spark Framework with Kotlin

2017.02.20 JJUG Night Seminor Kotlin

stormcat24

February 20, 2017

Transcript

44 slides
  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 


More Decks by stormcat24

Other Decks in Programming