Spring5.0 + Kotlinで1つのjarにHTTPサーバーとgRPCサーバーを相乗りさせてみた
Spring Boot 2.0.0 M1がリリースされました。以前のエントリで試した当時は 2.0.0.BUILD-SNAPSHOT
でありHTTPサーバーが起動している状態でgRPCクライアントを動かすとエラーになっていた。
2.0.0 M1のリリースに伴いHTTPサーバーとgRPCサーバーが1つのjarに相乗りできるようになっているか確認するのが今回のモチベーション。
次のようなアプリケーション構成を実現したい。
API Server
はHTTP1.1のリクエストのルーティング
とgRPCサーバー
のエンドポイントを提供する- API Serverに届いたリクエストは
Backend Server向け
のgRPCクライアント
からBackend Serverにリクエストする - API ServerとBackend Server間の通信はgRPCで行う
- エンドポイントには
HTTP1.1のリクエストのルーティング
とgRPCサーバー
の2つの通信方式を用意する - すべてのアプリケーションはSpring Boot 2.0.0 M1の上で動く
- Introducing Kotlin support in Spring Framework 5.0のエントリにあるようなSpring5.0で提供される新しい機能をつかう
ここからは試した過程での気づきなどをアウトプットしていく。
CommandLineRunnerでgRPCサーバーを起動する
HTTPルーティングが動いている状態で CommandLineRunner
をつかいgRPCサーバーを次のように起動させた。
@Configuration class GrpcServerRunner(private val appProperties: AppProperties, private val echoServer: EchoServer, private val taskServer: TaskServer) : CommandLineRunner, DisposableBean { private val logger = KotlinLogging.logger {} lateinit var server: Server override fun run(args: Array<String>) { val port = appProperties.grpc.server.port logger.info { "Starting gRPC Server ..." } val serverBuilder = NettyServerBuilder.forPort(port) serverBuilder.addService(echoServer) serverBuilder.addService(taskServer) server = serverBuilder.build().start() logger.info {"gRPC Server started, listening on port $port."} startDaemonAwaitThread() } }
Spring Boot 2.0.0 M1
で試したところ問題なく起動した!(2.0.0.BUILD-SNAPSHOT
ではエラーになっていたところ)
2.0.0 M1では300以上のissueやプルリクエストがマージされたので、その中のどれかで解消されたということだろう。
grpc-javaの 1.3.0
はエラーになる
Spring Bootには直接関係はないところであるが最新のgrpc-javaの1.3.0を使うとエラーになった。
java.lang.NoClassDefFoundError: io/netty/handler/codec/http2/internal/hpack/Decoder at io.grpc.netty.GrpcHttp2HeadersDecoder.<init>(GrpcHttp2HeadersDecoder.java:85) ~[grpc-netty-1.2.0.jar:1.2.0] at io.grpc.netty.GrpcHttp2HeadersDecoder$GrpcHttp2ServerHeadersDecoder.<init>(GrpcHttp2HeadersDecoder.java:135) ~[grpc-netty-1.2.0.jar:1.2.0] at io.grpc.netty.NettyServerHandler.newHandler(NettyServerHandler.java:109) ~[grpc-netty-1.2.0.jar:1.2.0] at io.grpc.netty.NettyServerTransport.createHandler(NettyServerTransport.java:132) ~[grpc-netty-1.2.0.jar:1.2.0] at io.grpc.netty.NettyServerTransport.start(NettyServerTransport.java:77) ~[grpc-netty-1.2.0.jar:1.2.0] at io.grpc.netty.NettyServer$1.initChannel(NettyServer.java:141) ~[grpc-netty-1.2.0.jar:1.2.0] ・・・
Netty 4.1.9
ではio.netty.handler.codec.http2.internal.hpack.Decoder
が削除されているのが原因。
gradleは次のようにgrpc-netty
のみ1.4.0-SNAPSHOT
のバージョンを指定することで解消できる。
buildscript { ext.grpc_version = "1.3.0" ext.grpc_version_snapshot = "1.4.0-SNAPSHOT" repositories { mavenCentral() maven { url "https://repo.spring.io/milestone" } maven { url 'http://repo.spring.io/plugins-release' } maven { url "https://oss.sonatype.org/content/repositories/snapshots" } } } ・・・ dependencies { ・・・ // grpc compile "io.grpc:grpc-netty:${grpc_version_snapshot}" compile "io.grpc:grpc-protobuf:${grpc_version}" compile "io.grpc:grpc-stub:${grpc_version}" compile "io.grpc:grpc-okhttp:${grpc_version}" compile "com.google.api.grpc:googleapis-common-protos:0.0.3" ・・・ }
BackendサーバーにはHTTPサーバーがいらない
BackendサーバーにはHTTPサーバーがいらないので次のように@SpringBootApplication
がついたメインクラスの起動でWebアプリケーションを動かさないようにした。
@SpringBootApplication @EnableConfigurationProperties(AppProperties::class) class Application { companion object { @JvmStatic fun main(args: Array<String>) { SpringApplicationBuilder(Application::class).web(WebApplicationType.NONE).run(*args) } } }
コード
今回試したコードはgithubに置いてあるので参考になれば嬉しい。
まとめ
- 理想の構成どおりにアプリケーションが組めた
- Spring5.0のkotlinサポートは魅力的であり実戦投入を検討したかったがgRPCとの相性が良くない印象であったが検証を経て問題ないことが確認できた
- いよいよSpring5.0の正式リリースに向けて整ってきている印象を受けた
- その他で利用しているSpringエコシステムの各種ライブラリとのSpring5.0/Spring Boot 2.0.0の相性が問題ないことを引き続き確認していき導入を検討していきたい