ロコガイド テックブログ

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

Pure Kotlin+gRPC(protobuf)なWebアプリを作るまで

f:id:ar_tama:20201117192124p:plain

この記事はロコガイド Advent Calendar 2020の7日目です。

みなさんこんにちは! ロコガイドサウナ部の部長こと、id:ar_tamaです。
自粛期間中に気が触れてついにテントサウナを購入してしまいました……が後悔していません。プライベートサウナ、最高です。おすすめですよ😉

さて、ロコガイドテックブログではFigma PluginやらIoTやらPython(Luigi)やらとさまざまな記事を書いてきましたが、今回は趣向を変えて、サーバサイドKotlinのお話をお届けします。

はじめに - ロコガイドとサーバサイドKotlin

サーバサイドKotlin、巷でもだいぶ聞かれるようになりましたよね。
JVM言語のひとつであるところのKotlinは、そのJavaとの親和性・互換性の高さから(資産を活用できる)Javaがメインの現場での採用事例をよく目にしますが、一方でLL言語などからの乗り換え事例はあまりないのが実情かと思います。
ロコガイドでは長らくメインの開発言語・環境としてRuby(on Rails)を活用してきました。しかし多くの開発者が直面しているように、我々もまた開発・サービスフェーズの変化によって、メンテナンス性や安定性の限界を迎えつつあるのが現状です。
こうした危機から脱する手法の1つとして、現状に即した言語やアーキテクチャでもってサービスの切り出し・リプレイスを行うというものが挙げられますが、現在ロコガイドではその候補としてサーバサイドKotlinの採用も検討しています*1
今回は既に実戦投入されている例として、gRPCをしゃべるWebアプリケーションを作るまでの流れと、ちょっとしたtipsの紹介をします。
この記事が、似たような事例を持つ皆さんの一助になれば幸いです。

Kotlinの用途と選定理由

まずは社内向けAPIとして(Railsアプリケーションの)トクバイで提供している機能の一部を切り出す、という用途にKotlinを採用しました。

  • 基本要件は既にRailsで提供している機能の焼き直し
    • 爆速プロトタイピング! といった趣の開発ではなく、静的型付けの恩恵を受けやすい
  • 新しい通信経路
    • トクバイ側に生やして更にFatにさせるよりは、新規に切り出すほうがよい
    • パイロット版として新しい試みを行うにもよいタイミング
    • たとえこの部分に問題が生じても、与える影響が少なく済む
  • トクバイのAndroidアプリでKotlinでの運用実績が十分にあり、メンバーに適宜教えを乞うことができる環境にあった

などがKotlinを選んだ理由です。
同時にGolangの採用も検討しましたが、社内でGolangを用いたアプリケーションの開発・運用経験が既にあり、Kotlinの打感を探るほうを優先させたかったこと、スケールメリットを得たいニーズがあまりなかったこと*2などから見送りとしました。

とはいえKotlinはRails脳な我々にはとっつきにくく、DDD本やクリーンアーキテクチャ本などを読みながらなんとかキャッチアップを進めているというのが現状です。
このあたりの七転八倒の様子も、別のエントリにてお伝えできたらと思っています:)

WAFと通信方式

gRPCを使うに至ったのは ノリと勢い 形式にJSON等の制約が存在せず、GraphQLのような柔軟な表現も必要なかったから…といったところでしょうか。
開発を始める少し前の4月にKotlin版が正式にサポートされたというのも、選択を後押しするきっかけとなりました。

またPure Kotlinでシンプルに、という指針に沿って、WAFとしては(Javaベースの重厚な)Springではなく(Kotlinベースの軽量な)Ktorを採用しています。
Ktorは公式にgRPCをサポートしている…というわけではないのですが、ApplicationEngineとして(内部で io.grpc.Server を呼び出す形で)定義することで、Ktor越しにgRPCサーバを立ち上げることが可能になります。
次のセクションでは実際に、protobufから生成されたgRPC Kotlinのコードにまつわるtipsをご紹介します。

tips: protobufから生成されるKotlinコード

まず、gRPC Kotlinの基本的な使い方はExamplesにまとまっています。

  • io.grpc.Server のインスタンスを start() させることでリクエストを待ち受けられる
  • 定義したprotobufを元に XXXProviderGrpcKt クラスが生成される
    • ここに定義されているクラスを継承してロジックを書いていく
  • Responseの組み立てにはBuilderパターンを使う

手元でビルドすると build.gradle.kts で指定されたディレクトリ以下に *.GrpcKt のコードが生成されるので、それらを読むことでなんとなく挙動を把握できます。
しかしsampleにないproto定義を実際にどのように扱ったらいいのか、というのは調べてもあまり出てこず、実践してみるしかありませんでした。
というわけで、自分で生成したコードから挙動を理解したものをいくつか抜粋します。

ユーザー定義型

message MyCustomResource {
  int64 owner_id = 2;
  string created_at = 3;
  string updated_at = 4;
}

responseと同様にbuilder経由でアクセスします。パスさえ通っていれば補完が効くのでお手軽です。

val myCustomResourceBuilder = MyCustomResource.newBuilder()
myCustomResourceBuilder.ownerId = 1234

resposeBuilderの setMyCustomResource は、builder, resource双方の型を受け入れられるため、buildしたものはこのままセットしてしまってokです。

responseBuilder.setMyCustomResource(myCustomResourceBuilder).build()

enum

enum MyCustomEnum {
  GREEN = 0;
  YELLOW = 1;
  RED = 2;
}

基本的にはKotlinのenumと同様にはたらくので、深く考えずに書くことができます。

// 数字からenum値を引く場合
MyCustomEnum.forNumber(0)

// 文字列からenum値を引く場合
MyCustomEnum.valueOf("GREEN")

repeated

message MyCustomResponse {
  repeated MyCustomResource resources = 1;
  string next_page_token = 2;
}

配列もひとつのリソースとして表現できます。

getResources(size).forEach {
    val resourceBuilder = MyCustomResource.newBuilder()
    resourceBuilder.ownerId = it.ownerId

    // push
    responseBuilder.addMyCustomResource(resourceBuilder)

    // 位置を指定することもできる
    responseBuilder.addMyCustomResource(1, resourceBuilder)
}

おわりに・次回予告

Kotlin赤ちゃん👶 の状態からのスタートで不安もありましたが、知識や知見をインプットしたり、諸先輩方に教えを乞うたりしながら、なんとか一通り動くものを作るところまで持っていくことができました。
このアプリケーションは既に本番で運用されており、たびたび機能追加やリファクタリングも行っています。
先に触れた習得までの七転八倒や、Androidアプリではおなじみのマルチモジュール化にトライした話など、ネタはいろいろ溜まりましたので、引き続き発信していきます:)
明日はCTO前田からの深イイ話です! 乞うご期待!

*1:前述のようなJavaによる既存のコード資産こそありませんが、そのぶんPure Kotlinでの構成を目指すことで、少しでも身軽なアプリケーションを構築することを目論んでいます

*2:JVMベースだと起動速度はどうしてもGolangに劣るので