前回の記事から始めた「サーバーサイドKotlin×gRPCコトハジメ」、今回はコードジェネレータの設定について設定や、protoファイルのオプションなどについて紹介します。
色々あるので全部を知りたい方は下記の公式ドキュメントを見ていただくと良いと思います。
developers.google.com github.com
ここでは基本的なよく使うものを説明していきます。
コードジェネレータの設定
build.gradleで記述していた、Protocol Buffersのコードジェネレータに関する設定についてです。
前回はほとんどデフォルトの設定で作成しましたが、いくつかオプションを紹介します。
protoファイルの配置ディレクトリ変更
自動生成の対象とするprotoファイルの配置ディレクトリを変更します。
デフォルトではprotoディレクトリが対象となっていますが、もし変えたい場合はsourceSetsへ下記のように変更してください。
sourceSets { main { // protoファイルの配置ディレクトリ proto { srcDir 'src/main/protobuf' } java { srcDirs 'src/main/grpc' } } }
Javaのビルド設定と同じように、 proto
ブロックに srcDir
で対象にしたいディレクトリのパスを記述します。
ここでは src/main/protobuf
を対象としています。
出力先ディレクトリの変更
生成するファイルの出力先ディレクトリを変更します。
前回の記事のサンプルでは分かりやすくするために、デフォルトの java
grpc
ディレクトリに出力していました。
しかし、実際は別のディレクトリに分かれているのが面倒だったり、自動生成のファイルは同じ場所にまとめたいなどあると思います。
build.gradleの generateProtoTasks
ブロックの中を、下記のように変更してください。
generateProtoTasks { all().each { task -> // Protocol Buffers関連のファイル出力先 task.builtins { java { outputSubDir = "generated" } } // gRPC関連のファイル出力先 task.plugins { grpc { outputSubDir = "generated" } } } }
task.builtins
の java
で指定しているのがProtocol Buffers関連のファイル(xxxOuterClassなど)の出力先、 task.plugins
の grpc
で指定しているのがgRPCプラグインで出力されるファイル(xxxGrpc)の出力先になります。
いずれも outputSubDir
というパラメータをすることで、 generatedFilesBaseDir
で指定したベースディレクトリ配下のこのディレクトリに出力されます。
今回はベースディレクトリである $projectDir/src/
の下の main/generated
というディレクトリにいずれのファイルも出力されるように設定しました。
また、出力先の変更に伴い、 sourceSets
の設定も変更してください。
sourceSets { main { proto { srcDir 'src/main/protobuf' } java { // ビルド対象のディレクトリを変更 srcDirs 'src/main/generated' } } }
cleanタスク
ついでに出力したファイルを削除するための clean
タスクも作っておきましょう。
トップレベルの階層に下記の記述を追加します。
clean { delete "$protobuf.generatedFilesBaseDir/main/generated" }
delete
に出力先ディレクトリのパスを指定しています。
そして下記のコマンドの実行、もしくはIDEで clean
タスクを実行すると、generatedディレクトリが削除されます。
./gradlew clean
変更後のbuild.gradle全体
ここまで紹介した変更を加えたbuild.gradleを、参考として載せておきます。
plugins { id("org.springframework.boot") version "2.1.6.RELEASE" id("org.jetbrains.kotlin.jvm") version "1.3.21" id("org.jetbrains.kotlin.plugin.spring") version "1.3.21" id("io.spring.dependency-management") version "1.0.7.RELEASE" id("com.google.protobuf") version "0.8.9" id( "java") } sourceSets { main { proto { srcDir 'src/main/protobuf' } java { srcDirs 'src/main/generated' } } } group = "com.example.grpc.kotlin" version = "0.0.1-SNAPSHOT" sourceCompatibility = JavaVersion.VERSION_1_8 def grpcVersion = "1.10.0" repositories { mavenCentral() } dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") compile("io.github.lognet:grpc-spring-boot-starter:3.3.0") testImplementation("org.springframework.boot:spring-boot-starter-test") } compileKotlin { kotlinOptions { freeCompilerArgs = ['-Xjsr305=strict'] jvmTarget = '1.8' } } protobuf { protoc { artifact = "com.google.protobuf:protoc:3.5.1-1" } plugins { grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } } generateProtoTasks { all().each { task -> task.builtins { java { outputSubDir = "generated" } } task.plugins { grpc { outputSubDir = "generated" } } } } generatedFilesBaseDir = "$projectDir/src/" } clean { delete "$protobuf.generatedFilesBaseDir/main/generated" }
protoファイルのオプション
protoファイルで設定できるいくつかのオプションを紹介します。
まず、前回の記事で使用した「Greeter.proto」を下記のように変更します。
syntax = "proto3"; option java_package = "com.example.grpc.kotlingrpcexample.proto"; // ①出力先パッケージ名の指定 option java_outer_classname = "GreeterProtobuf"; // ②出力ファイル名の指定 option java_multiple_files = true; // ③messageを別ファイル出力 service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; } // ④Enumの定義 enum MessageType { NONE = 0; NORMAL = 1; SPECIAL = 2; }
このファイルのコメントに沿って、順番に説明していきます。
①出力先パッケージの指定
これは前回も紹介していますが一応。
option java_package
で生成するクラスのパッケージを指定できます。
option java_package = "com.example.grpc.kotlingrpcexample.proto";
ちなみに省略するとデフォルトパッケージになります。
②出力ファイル名の指定
ジェネレータで出力されるファイル名は、デフォルトで ${ファイル名}OuterClass
になっていました。
(サンプルではGreeterOuterClass)
このファイル名を変えたい場合は、 option java_outer_classname
で指定します。
option java_outer_classname = "GreeterProtobuf";
上記の例では、「GreeterProtobuf」という名前で出力されるようになります。
③messageを別ファイル出力
ジェネレータで出力されたファイル(デフォルトでGreeterOuterClass)には、 service
message
の定義に紐付いたクラス、メソッドが含まれています。
messageで定義しているRequest、Responseのクラスもstaticなインナークラスとして定義されています。
この形では扱いづらいこともあるため、出力するファイル、クラスを分離することができます。
option java_multiple_files
を使用します。
option java_multiple_files = true;
このオプションをtrueにすることで(デフォルトはfalse)、OuterClassからはmessageに関する定義はなくなり、下記のBuilderインターフェース、実装クラスがそれぞれ別ファイルで出力されます。
- HelloReply
- HelloReplyOrBuilder
- HelloRequest
- HelloRequestOrBuilder
④Enumの定義
protoファイルでは、Enumを定義することもできます。
下記のように各要素に数値を指定して記述します。
enum MessageType { NONE = 0; NORMAL = 1; SPECIAL = 2; }
proto3のバージョンでは、 0
を指定しないとエラーになってしまうので、必ず指定するようにしてください。
0で定義している要素が、このEnumのデフォルト値として扱われます。
そして、定義したEnumをmessageのプロパティの型に指定することができます。
message HelloReply { string message = 1; MessageType type = 2; }
ジェネレータを実行すると MessageType
というEnumファイルも生成される(java_multiple_filesがfalseの場合はOuterClassの中に定義される)ため、あとは通常のJavaのEnumと同じように使用できます。
val replyBuilder = HelloReply.newBuilder() .setMessage("Hello " + request.name) .setType(MessageType.NORMAL)
おまけ:messageを型に指定
先ほど定義したEnumをプロパティの型に指定しましたが、同じようにmesageを型にすることもできます。
message HelloRequest { string name = 1; // messageのUserを型に指定 User user = 2; } message User { int32 id = 1; }
上記の例では「User」というmessageを定義し、それをHelloRequestの2番目のプロパティの型に指定しています。
protoファイルのパッケージ構成パターン
ここまでprotoファイルは「Greeter.proto」という1つのファイルに全て記述してきましたが、中のserviceやmessageをファイル自体で分けることもできます。
protoファイルの配置場所として決められたディレクトリ内にさえ置かれていれば、その中のディレクトリ階層やファイル構成は関係なく、全て生成してくれます。
最後に、いくつか想定されるファイル構成のパターンを参考として紹介します。
serviceとその中で使うmessage、Enumを全て入れる
まずは1つのファイルに全部を入れるパターン。
service単位でファイルは分けて、その中で使うmessage、Enumの定義も同梱する構成。
今回のGreeter.protoと同じですね。
service、message、Enumを完全分離
service、message、Enumそれぞれでファイルを作るパターン。
今回の例で言うと、下記の4ファイルを作るイメージです。
- GreeterService.proto
- HelloRequest.proto
- HelloReply.proto
- MessageType.proto
Javaの出力ファイルと近い構成になりますね。
messageを一つにまとめる
Request、Responseのmessage、その中で参照するオブジェクトがセットのファイルをるパターン。
- GreeterService.proto
- HelloMessages.proto
のようなファイルを作り、「HelloMessages.proto」にHelloRequest、HelloReply、MessageTypeが定義されているイメージです。
今回はここまで
今回はProtocol Buffersのコードジェネレータや、protoファイルの記述について紹介しました。
これで基本的な構成ができあがりました。
次回はgRPCでのインタセプタやエラーハンドリングなどの実装方法について、紹介する予定です。