はじめに
初めまして、株式会社ビデオマーケットでサーバーサイドエンジニアを担当しているrhyskです。
弊社では以前よりサーバーサイド言語にKotlinを採用していましたが、RestAPIで提供しているサービスしかなくgRPCを利用したサービスはまだありませんでした。
今年から新たなプロジェクトが開始した折にメンバーからgRPCを使ってみたいとの声が多数上がったため、今回採用することになりました。
今更ではありますがKotlinでgRPCを動かす際のチュートリアルを作成したのでぜひ参考にしてみてください。
フレームワークはSpring Bootを使用します。
gRPCとは
gRPCとはGoogleが開発したオープンソースのRPC(Remote Procedure Call)システムです。 サーバとクライアント間はHTTP/2で通信を行います。
Protocol Buffers(プロトコルバッファー)と呼ばれるインタフェース記述言語(IDL)を書くだけで、サーバーとクライアントでやりとりするためのソースコードを自動で生成できます。
つまり、Protocol Buffersは、「通信するための仕様を決めることができ、その仕様通りに実装することができる」ということです。
メンテされない仕様書を作る必要なく、インタフェース仕様の会話ができるのは素晴らしいですね。
準備するもの
- MacBook Pro
- IDE
- gRPC UI(gRPCのクライアントツール)
環境構築
Spring Bootの雛形作成
Kotlinで使用できるフレームワークも結構増えてきましたがサーバーサイドで使える機能も多いのでここではSpring Bootを使用することにします。 Spring Bootの雛形を作るための専用のサイトSpring Initializrがあるのでここから必要な項目を入力してダウンロードしてください。
gRPCライブラリのインストール
以下のライブラリをダウンロードしてきた雛形にある build.gradle.kts に適用していきます。
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import com.google.protobuf.gradle.* // これを追加↓↓↓ val protobufVersion = "3.17.0" val grpcVersion = "1.16.1" val grpcKotlinVersion = "0.1.2" val grpcSpringBootVersion = "4.5.6" // これを追加↑↑↑ plugins { id("org.springframework.boot") version "2.5.5" id("io.spring.dependency-management") version "1.0.11.RELEASE" kotlin("jvm") version "1.5.31" kotlin("plugin.spring") version "1.5.31" // これを追加↓↓↓ id("idea") id("com.google.protobuf") version "0.8.16" // これを追加↑↑↑ } group = "com.example" version = "0.0.1-SNAPSHOT" java.sourceCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") developmentOnly("org.springframework.boot:spring-boot-devtools") testImplementation("org.springframework.boot:spring-boot-starter-test") // これを追加↓↓↓ implementation("io.github.lognet:grpc-spring-boot-starter:$grpcSpringBootVersion") implementation("io.grpc:grpc-kotlin-stub:$grpcKotlinVersion") // これを追加↑↑↑ } tasks.withType<KotlinCompile> { kotlinOptions { freeCompilerArgs = listOf("-Xjsr305=strict") jvmTarget = "11" } } tasks.withType<Test> { useJUnitPlatform() } // これを追加↓↓↓ protobuf { protoc { artifact = "com.google.protobuf:protoc:$protobufVersion" } plugins { id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion" } id("grpckt") { artifact = "io.grpc:protoc-gen-grpc-kotlin:$grpcKotlinVersion" } } generateProtoTasks { ofSourceSet("main").forEach { it.plugins { id("grpc") id("grpckt") } } } } // これを追加↑↑↑
Protocol Buffersを作成
今回はユーザーIDを渡すとメールアドレスが返ってくるようなものをIFとして定義してみます。
src/main/proto
ディレクトリを作成してそこに以下のような user_provider.proto
を作ってみることにします。
user_provider.proto
syntax = "proto3"; option java_package = "com.example.kotlingrpc.proto"; // ユーザーサービス service UserService { // ユーザー取得 rpc GetUser(GetUserRequest) returns (GetUserResponse); } // ユーザー取得リクエスト message GetUserRequest { int32 id = 1; // ユーザーID } // ユーザー取得レスポンス message GetUserResponse { string mail = 1; // メール }
ソースコードの生成
作ったprotoファイルに基づいて通信するためのサーバーのソースコードを自動生成します。
ターミナルなどでプロジェクトのルートディレクトリに移動して以下のコマンドを実行します。
BUILD SUCCESSFUL
と表示されたらソースコードが build/generated
配下に自動生成されています。
./gradlew generateProto
実装
自動生成されたソースコードを継承してメソッドをオーバーライドします。
ここではサンプルとして実装しているのでユーザーID1、2の時に何かしらメールアドレスが返ってくるような処理にしています。
UserController.kt
package com.example.kotlingrpc.controller import com.example.kotlingrpc.proto.UserProvider import com.example.kotlingrpc.proto.UserServiceGrpcKt import org.lognet.springboot.grpc.GRpcService @GRpcService class UserController : UserServiceGrpcKt.UserServiceCoroutineImplBase() { /** * ユーザー取得 */ override suspend fun getUser(request: UserProvider.GetUserRequest): UserProvider.GetUserResponse { return UserProvider.GetUserResponse.newBuilder().setMail(fetchMail(request.id)).build() } /** * メールアドレスを返す */ private fun fetchMail(id: Int) = when (id) { 1 -> "video@videomarket.co.jp" 2 -> "market@videomarket.co.jp" else -> "" } }
サーバー起動
ターミナルなどでプロジェクトのルートディレクトリに移動して以下のコマンドを実行します。
./gradlew bootRun
サーバーが起動すると以下のようなログが流れます。
2021-09-30 18:35:06.777 INFO 52485 --- [ restartedMain] o.l.springboot.grpc.GRpcServerRunner : gRPC Server started, listening on port 6565. 2021-09-30 18:35:06.784 INFO 52485 --- [ restartedMain] c.e.kotlingrpc.KotlinGrpcApplicationKt : Started KotlinGrpcApplicationKt in 1.575 seconds (JVM running for 1.938)
動作確認
gRPCサーバーが立ち上がったら実行してみましょう。
gRPC UIなどのクライアントツールを使うと動作確認が簡単です。
ポート6565で立ち上がったサーバー情報にアクセスするためには以下のコマンドを実行します。
grpcui --plaintext localhost:6565
上記のコマンドを実行するとリフレクションAPIがサポートされていない旨のメッセージが出ると思います。
Failed to compute set of methods to expose: server does not support the reflection API
その場合は以下のようなクラスを用意してリフレクションサポートをしてあげてください。 こうすることでgRPCで提供しているサービスとメソッドを知ることができるようになります。
GRpcBuildConfig.kt
package com.example.kotlingrpc import io.grpc.ServerBuilder import io.grpc.protobuf.services.ProtoReflectionService import org.lognet.springboot.grpc.GRpcServerBuilderConfigurer import org.springframework.stereotype.Component @Component class GRpcBuildConfig: GRpcServerBuilderConfigurer() { override fun configure(serverBuilder: ServerBuilder<*>) { serverBuilder.addService(ProtoReflectionService.newInstance()) } }
上記のメッセージが解消されたらもう一度サーバーを再起動してgrpcuiコマンドも再実行します。
gRPC UIが立ち上がるとidを入力して実行してみます。
無事メールアドレスが返却されました!
終わりに
いかがでしたか?
KotlinもGradleもSpringもgRPCもその他のライブラリも入れ替わりがかなり早いですが、どんどんシンプルに実装できるように進化しているのでしっかりついていきたいですね!
弊社のサービスもKotlin × gRPC × Spring Bootで稼働し始めたのでまた別の機会に細かい情報を発信できればと思います。