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

Road to Kotlin 〜10年続くPerl運用からの脱却〜

Road to Kotlin 〜10年続くPerl運用からの脱却〜

「後夜祭.kt 2024」の登壇資料です。
https://lycorptech-jp.connpass.com/event/322264/

More Decks by LINE Digital Frontier - TECH

Other Decks in Technology

Transcript

  1. ⽬次 01. はじめに 1.1 LINEマンガとは 1.2 ⾃⼰紹介 02. Kotlin移⾏の経緯 2.1

    サーバーサイドのPerl運⽤の限界 2.2 Kotlin採⽤の理由 03. Kotlin移⾏⽅法 3.1 移⾏の注意点 3.2 リクエストからレスポンスまで 3.3 Kotlinの切り替えと切り戻し 3.4 Perl/Kotlinのレスポンス⽐較 3.5 AIの活⽤ 04. おわりに 3.1 まとめ
  2. LINEマンガについて • 2013年 サービス開始 • 「Google Play ベスト オブ 2023」において「ベ

    ストアプリ 2023」と「エンターテイメント部 ⾨」⼤賞を受賞 • 国内マンガアプリ累計ダウンロード数で1位を記録 ※ (2013年4⽉〜2022年12⽉) iOS & Google Play合計 / 出典:data.ai(App Annieより名称変更 ) • 親会社のWEBTOON Entertainmentは⽶国NASD AQに2024年6⽉27⽇に上場
  3. リクエストからレスポンスまで DB Gateway server Perl Server (Plack) Kotlin Server (JVM)

    Redis Elasticsear ch etc… [1] Gateway serverからPerl / Kotlin serverに割り振る [1] [2] [3] [5] [4] [2] [3] [4] [5] [6]
  4. リクエストからレスポンスまで [1] Gateway serverからPerl / Kotlin serverに割り振る メリット • Gateway

    serverの数だけ、⼀台ずつPerl/Kotli n serverに寄せられる • ⼀度設定すれば、ワンライナーでリリース/ ロールバックが可能 デメリット • どのアクセスがKotlinに⾏くかはランダム • ロジックの⼀部だけをKotlinに寄せることが難 しい # Gateway serverのnginx設定 if ( -f /home/www/html/P2J_API_FOO) { rewrite ^(/pl|)(/api/foo)$ /kt$2 last; } # Release時 for i in $(echo api-gateway{001..005}-server); ssh user@$i "t ouch /home/www/html/P2J_API_FOO"; done
  5. リクエストからレスポンスまで DB Gateway server Perl Server (Plack) Kotlin Server (JVM)

    Redis Elasticsear ch [1] [2] [3] [4] [5] [6] [8] [7] [9] [10] etc… [2] Kotlin serverに100%振り、Kotlinサーバー内で制御
  6. Kotlinの切り替えと切り戻し class FooService( private val centralDogma: CentralDogmaState<ApiConfig>, ) { fun

    fooResponse(): FooResponse { val perlResponse = getPerlResponse() val kotlinResponse = getKotlinResponse() return FooResponse( componentA = getComponentA(kotlinResponse, perlResponse), // componentB, C, E... ) } private fun getComponentA( kotlinResponse: FooResponse, perlResponse: FooResponse, ): ComponentA { return if (fooSettings().useKotlinImplementation.componentA) { kotlinResponse.componentA } else { perlResponse.componentA } } } [2] Kotlin serverに100%振り、Kotlinサーバー内で制御
  7. Perl/Kotlinのレスポンス⽐較 private fun compareWithPerlResponse( perlResponse: FooResponse, kotlinResponse: FooResponse, ): List<JsonNodeDiffInfo>

    { // perl serverからのレスポンス val perlResponseJsonNode = convertToJsonNode(perlResponse) // kotlin内の処理で⽣成されたレスポンス val responseJsonNode = convertToJsonNode(kotlinResponse) // 2つのJsonNodeを⽐較し、差分情報のリストを取得 return compareJsonNodes( perlResponseJsonNode, responseJsonNode, ).filter { diffInfo -> // 特定のキーを持つ差分のみを⽐較対象にする !skipComparableKeyPrefixes.any { prefix -> if (diffInfo.path.length > prefix.length) { diffInfo.path.startsWith("$prefix/") } else diffInfo.path == prefix } } } private fun convertToJsonNode(response: FooResponse): JsonNode { val responseJson = objectMapper.writeValueAsString(response) return objectMapper.readTree(responseJson) // return JsonNode } JsonNode classで再帰的にチェック。差分をログに出⼒
  8. Perl/Kotlinのレスポンス⽐較 data class JsonNodeDiffInfo( val path: String, val reason: String,

    ) fun compareJsonNodes( node1: JsonNode, node2: JsonNode, path: String = "", ): List<JsonNodeDiffInfo> { // JsonNodeのタイプに基づいて⽐較を⾏う return when { node1.isObject -> compareObjectNodes(node1, node2, path) node1.isArray -> compareArrayNodes(node1, node2, path) node1 != node2 -> listOf(createDiffInfo(path, "$node1 != $node2")) else -> emptyList() } } private fun createDiffInfo(path: String, reason: String): JsonNodeDiffInfo { return JsonNodeDiffInfo( path = path, reason = reason, ) } private fun compareObjectNodes( node1: JsonNode, node2: JsonNode, path: String, ): List<JsonNodeDiffInfo> { return node1.fields().asSequence().toList().flatMap { (key, value) -> val newPath = "$path/$key" if (node2.at("/$key").isMissingNode) { // 2つ⽬のJsonNodeに同じフィールドが存在しない場合、差分情報を⽣成 listOf(createDiffInfo(newPath, "missing path")) } else { // 同じフィールドが存在する場合、そのフィールドの値(JsonNode)に対して再帰的に⽐較を⾏う compareJsonNodes(value, node2.get(key), newPath) } } } private fun compareArrayNodes( node1: JsonNode, node2: JsonNode, path: String, ): List<JsonNodeDiffInfo> { return node1.flatMapIndexed { index, value -> val newPath = "$path/$index" if (node2.at("/$index").isMissingNode) { // 2つ⽬のJsonNodeに同じインデックスの要素が存在しない場合、差分情報を⽣成 listOf(createDiffInfo(newPath, "missing path")) } else { // 同じインデックスの要素が存在する場合、その要素(JsonNode)に対して再帰的に⽐較を⾏う compareJsonNodes(value, node2.get(index), newPath) } } }
  9. AIの活⽤ # 発売⽇当⽇かどうかを返却 use Time::Piece; sub is_today { my $date_str

    = shift; my $input_date = Time::Piece->strptime($date_str, '%Y-%m-%d %H:%M:%S'); my $today = localtime; return $input_date->ymd eq $today->ymd; } Perl -> Kotlinの書き換え作業はAIの得意分野