Share
Embed
Transcript
44 slides
-
stormcat24 ‣ CyberAgent, Inc. ‣ FRESH! ‣ DevOps / Docker
Comedian ‣ ワタシコトリンチョットカケルアルヨ ‣ 最近の興味はRust -
technology stack ‣ Microservices Architecture ‣ Full Dockernized ‣ Amazon
Web Services ‣ EC2 Container Services(ECS) ‣ Golang -
Golang と FRESH ‣ 2016/01のリリースから一貫してGolangを主要言語として採用 ‣ 1Service = 1Container =
1Repository ‣ Public/Internal問わず十数個のMicroservicesが誕生 ‣ シンプル -
しかしつらくなってきた ‣ 基本的に筋力に頼る言語 ‣ 記述量が多い ‣ 高階関数ほしい ‣ 生産性を上げるためのコード生成 ‣
タイプ量 成果と満足感❓ ‣ ゴリゴリとAPIを書いていくのはしんどいというメンバーの総意 -
Microservicesの特性を活かす ‣ 言語はServiceによって変えられる ‣ 将来的に変えることも想定していた ‣ Serviceの特性によって、適材適所なものを選んでいくのが重要 ‣ ミドル層、低レイヤー層は引き続きGolang ‣
新たに、一つの基軸言語を選ぶ(乱立は(・A・)イクナイ!!) -
Spark Framework is ‣ Java8ベースのマイクロWebフレームワーク ‣ Apache Sparkとは別物 ‣ ググラビリティ・・・
‣ Lambda式を利用 ‣ Imspired by Sinatra ‣ Routing/Filter等最低限の機能、DIとか無いです -
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") + "!"); } } -
Spark Framework with Kotlin ‣ Java8 Lambda式だけでは満足できない ‣ Spark Frameworkのシンプルさと、Kotlinの良さを活かす
‣ 必要なライブラリを追加して、自力で統合していくスタイル ‣ SpringのようにIntegrationが充実しているわけではないが、相 性の悪さだったり、迷いはあまり無い -
Kotlin使ってこ fun main(args: Array<String>) { get("/echo", { req, res
-> "Hello, ${req.queryParams("name")}!" }) } -
Spark with Kotlinお品書き ‣ Controller / Routing ‣ Filters ‣
ResponseTransformer(JSON) ‣ data class ‣ 拡張関数 ‣ Guice(Dependency Injection) ‣ 愚直なDI ‣ okhttp ‣ hystrix ‣ Metrics(Jolokia) -
Controller package io.stormcat.controller import spark.* class EchoController {
val echo = Route { req, res -> "Hello, ${req.queryParams("name")}!" } } -
Routing package io.stormcat import io.stormcat.controller.EchoController import spark.Spark.* fun
main(args: Array<String>) { get("/echo", EchoController().echo) } -
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") } } -
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 ‣ 認証や、横断的関心事の解決 -
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 をサポート -
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内に気軽に書けて(・∀・)イイ!! -
拡張関数 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に関数を生やして取れるようにしてしまう ‣ ラップするより拡張という選択 -
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 -
愚直な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から取得 -
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 -
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) } } -
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として設定 -
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 -
まとめ ‣ Spark + Kotlin十分運用していけるし、現実的な選択肢になった ‣ Microservicesトレンドの中、シンプルなものが求められている ‣ Server Side
Kotlinの波は少しずつ来ている ‣ Kotlin気持ち(・∀・)イイ!!