LoginSignup
28
17

More than 5 years have passed since last update.

KotlinでSpringBootでgRPCサーバーを立てた時の設定メモ

Last updated at Posted at 2017-07-23

概要

趣味で作るアプリでgRPCを使った通信をしてみようと思い設定した時のメモです。
build.gradleで何を設定する必要があるかについて主に書いています。

Kotlin、SpringBoot、gRPCについては、細かく説明しません。
こちらの設定についてのメモです。

技術選択の気持ち

なぜKotlin?

Java8のOptionalは手に馴染まない。isPresentとか書くの面倒くさい。
最近周りで流行している。
かわいい。

サイバーエージェントでは、Androidだけでなく、サーバーサイドでもKotlin使っているところがある。(※1 関連資料)

なぜSpringBoot?

Javaでアプリケーション書く時のデファクトスタンダード(だと思ってる)。

なぜgRPC?

触ってみたいから。
趣味で作るアプリなので使ったことがないものを使いたい。

メルカリのバックエンドでも利用されていて(※2 関連資料)、今後マイクロサービス化とともに広まっている可能性がある。

build.gradleの設定

buildscript

buildscript {
    ext.kotlin_version = '1.1.2'

    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" // -- (1)
        classpath "org.springframework.boot:spring-boot-gradle-plugin:1.5.3.RELEASE"
        classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.1" // -- (2)
    }
}

(1) kotlin-allopen

KotlinでSpringBootを使うために入れています。Kotlinでは、デフォルトでfinal classになるため、SpringFrameworkの@Serviceなどのアノテーションを使ったAutowiredができません。

kotlin-allopenを使うことで、特定のアノテーションが付いているクラスをopenにすることができます。

また、apply plugin: 'kotlin-spring'によって、SpringFrameworkで使われるアノテーションがついているクラスをopenにしてくれます。

参考:
Compiler Plugins - Kotlin Programming Language

(2) protobuf-gradle-plugin

Protocol Buffersのビルドをするためのプラグイン。

apply plugin

先ほど出てきましたが、SpringFrameworkを使うためkotlin-springを入れておきます。

apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'org.springframework.boot'
apply plugin: 'com.google.protobuf'
apply plugin: 'application'

repositories

grpc-spring-boot-starterjcenter()にあるので追加しています。

repositories {
    mavenCentral()
    jcenter()
}

sourceSets

Protocol Buffersが生成するソースのディレクトリを追加しておきます。

sourceSets {
    main.kotlin.srcDirs += 'src/main/kotlin'
    main.java.srcDirs += 'src/main/java'
    main.java.srcDirs += 'src/main/generated-proto'
}

dependencies

def grpcVersion = '1.5.0'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"

    // grpc
    compile "io.grpc:grpc-netty:${grpcVersion}" // -- (1)
    compile "io.grpc:grpc-protobuf:${grpcVersion}" // -- (2)
    compile "io.grpc:grpc-stub:${grpcVersion}" // -- (3)

    // spring-boot
    compile "org.springframework.boot:spring-boot-starter-web:1.5.3.RELEASE"
    compile "org.springframework.boot:spring-boot-actuator:1.5.3.RELEASE"
    compile "org.lognet:grpc-spring-boot-starter:2.0.4" // -- (4)
}

(1) grpc-netty

書かなくてもorg.lognet:grpc-spring-boot-starterの依存で入ります。
ただ、バージョン1.4.0が入って通信に失敗するので、1.5.0を明示的に書いてます。

(2) grpc-protobuf

Protocol Buffersが生成したクラスの中で使っているので必須。

(3) grpc-stub

Protocol Buffersが生成したクラスの中で使っているので必須。

(4) grpc-spring-boot-starter

@GRpcServiceをつけるだけで、SpringBootがgRPCのサービスとして起動してくれるようになるOSS。
Eurekaも一緒に使えるようなので、ロードバランシングもできそう。(キタコレ!)

Apache License 2.0。

protobuf

Protocol Buffersのコンパイル設定を書きます。

参考: protobuf-gradle-plugin

protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.3.0' // -- (1)
    }
    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:1.5.0" // -- (2)
        }
    }
    generateProtoTasks { // -- (3)
        all().each { task ->
            task.builtins {
                java {
                    outputSubDir = 'generated-proto'
                }
            }
            task.plugins {
                grpc {
                    outputSubDir = 'generated-proto'
                }
            }
        }
    }
    generatedFilesBaseDir = "$projectDir/src/" // -- (4)
}

(1) protoc

Protocol Buffersのコンパイラを指定。
前述した、grpc-protobufやgrpc-stubのバージョンとprotocのバージョンには依存関係があり、バージョンがズレていると、Protocol Buffersが生成したjavaのコンパイルに失敗することがあります。(依存関係があるのに依存関係チェックしてくれない。。)

どちらも最新版にしておけば、動くと思います。

(2) protoc-gen-grpc-java

protocでJavaを生成するためにPluginを入れます。
Protocol Buffersは、GoやPHPなどの様々な言語への出力ができるようになっています。残念ながらまだKotlinを生成できないので、Javaにしておきます。

(3) generateProtoTasks

生成したJavaファイルの配置先を設定します。
task.builtinsのファイルは、デフォルトで"${generatedFilesBaseDir}/main/java"の下に置かれるようです。
task.pluginsのファイルは、設定を入れないとどこにも出力されませんでした。

ここでは、Protocol Buffersが出力したことが分かりやすいように、generated-protoとしています。

(4) generatedFilesBaseDir

生成したJavaファイルの配置先を設定します。
(3)で設定したものと合わせて、"${generatedFilesBaseDir}/main/${outputSubDir}"に生成されます。

clean

gradle clean時に生成ファイルが消えるように設定しておきます。

clean {
    delete "$projectDir/src/main/generated-proto"
}

bootRepackage

fat jarを作るためにbootRepackageいれておきます。

bootRepackage  {
    executable = true
}

build.gradle設定まとめ

build.gradleの設定はここまでです。ソースはこちら

その他つらつらと

.gitignore

Protocol Buffersのコンパイルで生成されるファイルがgitに入らないようにignoreしましょう。

generated-proto

Dockerfile

javaが入っている環境で作ったfat jarを実行するだけのDockerイメージを作ると、runするだけでサーバー上で動かせます。
ありがとうDocker。

Dockerfile
FROM java:8-jre

ADD sample-1.0.0.jar /app/

CMD ["java", "-jar", "/app/sample-1.0.0.jar"]

EXPOSE 6565

サーバーの動作確認

polyglotを利用すると簡単な動作確認ができます。
protoファイル、通信先、実行するメソッドを指定すると、通信結果をjsonで吐き出してくれます。

コマンド
echo "{'name': 'world'}" | java -jar ~/polyglot.jar \
--command=call --endpoint=localhost:6565 \
--full_method=helloworld.Greeter/SayHello \
--proto_discovery_root=src/main/proto/ \
--use_tls=false
実行結果
[main] INFO me.dinowernli.grpc.polyglot.Main - Polyglot version: 1.2.0
[main] INFO me.dinowernli.grpc.polyglot.Main - Loaded configuration: 
[main] INFO me.dinowernli.grpc.polyglot.command.ServiceCall - Creating dynamic grpc client
[main] INFO io.grpc.internal.ManagedChannelImpl - [ManagedChannelImpl@25af5db5] Created with target localhost:6565
[main] INFO me.dinowernli.grpc.polyglot.command.ServiceCall - Making rpc with 1 request(s) to endpoint [localhost:6565]
[main] INFO me.dinowernli.grpc.polyglot.grpc.DynamicGrpcClient - Making unary call
[grpc-default-executor-1] INFO me.dinowernli.grpc.polyglot.io.LoggingStatsWriter - Got response message
{
  "message": "Hello world"
}

[grpc-default-executor-1] INFO me.dinowernli.grpc.polyglot.io.LoggingStatsWriter - Completed rpc with 1 response(s)

curlの方が簡単だけど、gRPCだとcurlできないので仕方ない。

KotlinでのSpringFrameworkのコンストラクタインジェクションの書き方

kotlin
@Service
class SampleService @Autowired constructor(val repository: SampleRepository) {
}

Javaにデコンパイルすると以下のようになって、確かに動きそう。(Kotlinなのでgetterが勝手に生えるw)

Java.decompiled
@Service
public class SampleService {
   @NotNull
   private final SampleRepository repository;

   @NotNull
   public SampleRepository getRepository() {
      return this.repository;
   }

   @Autowired
   public SampleService(@NotNull SampleRepository repository) {
      Intrinsics.checkParameterIsNotNull(repository, "repository");
      super();
      this.repository = repository;
   }
}

関連資料

※1 GRPCの実践と現状での利点欠点 / Go Conference 2016 Spring
※2 サイバーエージェントのKotlin勉強会「CA.kt」がスタートーーベータ版から開発で利用している主催者に話を聞きました

28
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
17