scalaでgRPCする

最近gRPCが楽しくて仕方ない. いろいろやったけど,やっぱりgoogleが作っただけあって,goで書いてあるサンプルは多いし,goでgRPCは使いやすい(その話は別のところで話したい).

のだけれど,どうも最近scalaを書くことが多く,そしてscalaでgRPCしているサンプルがあまりないのでメモを残しておく.

あと,gRPCの説明とかは特にしないので,そいうのは別の記事を参考にしていただきたい. チュートリアル的な実装も,goでやったほうが楽だしわかりやすいと思うよ(俺は).

ProtocolBufferを使えるようになるまで

という話の前に,

xuwei-k.github.io

とても参考になるし,ここのサンプルだけで今から書こうとしていることのほとんどすべてが網羅されている. むしろこれをちゃんと読めばこの記事は読まなくていい.

基本的にはこれとまったく同じで,ScalaPBを使う.

project/plugins.sbt あたりに,

addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.12")

libraryDependencies += "com.trueaccord.scalapb" %% "compilerplugin" % "0.6.6"

という記述をしておく.

で,build.sbt

import com.trueaccord.scalapb.compiler.Version.{grpcJavaVersion, scalapbVersion, protobufVersion}

/* 中略 */

libraryDependencies ++= Seq(
/* 中略 */
  "io.grpc" % "grpc-netty" % grpcJavaVersion,
  "com.trueaccord.scalapb" %% "scalapb-runtime" % scalapbVersion % "protobuf",
  "com.trueaccord.scalapb" %% "scalapb-runtime-grpc" % scalapbVersion,
  "io.grpc" % "grpc-all" % grpcJavaVersion
)

PB.targets in Compile := Seq(scalapb.gen() -> ((sourceManaged in Compile).value / "protobuf-scala"))
PB.protoSources in Compile += (baseDirectory in LocalRootProject).value / "protocol"

みたいなことを書いておく. 最後のprotoSources では,実際に .proto ファイルを入れておくディレクトリを指定した.

これで,$ sbt compile とかしておけば,ProtocolBufferからコードが自動生成される.

gRPCサーバの中身を書く

自動生成されたコードだけでは,ただのライブラリでしかないので,ここからサーバを起動できるようにしよう.

package grpc

import io.grpc.{Server, ServerBuilder}

// ProtocolBufferから自動生成されたライブラリたち
import users.users.{RequestType, UsersGrpc}

import scala.concurrent.ExecutionContext


object GrpcServer {
  private val logger = Logger.getLogger(classOf[GrpcServer].getName)

  def main(args: Array[String]): Unit = {
    val server = new GrpcServer(ExecutionContext.global)
    server.start()
    server.blockUnitShutdown()
  }

  private val port = sys.env.getOrElse("SERVER_PORT", "50051").asInstanceOf[String].toInt
}

class GrpcServer(executionContext: ExecutionContext) { self =>
  private val port = sys.env.getOrElse("SERVER_PORT", "50051").asInstanceOf[String].toInt
  private[this] var server: Server = null

  def start(): Unit = {
    server = ServerBuilder.forPort(port).addService(
      UsersGrpc.bindService(new UsersImpl, executionContext)
    ).build.start
    Logger.info("gRPC server started, listening on " + port)
    sys.addShutdownHook {
      Logger.info("*** shutting down gPRC server since JVM is shutting down")
      self.stop()
    }
  }

  def stop(): Unit = {
    if (server != null) {
      Logger.info("*** gRPC server shutdown")
      server.shutdown()
    }
  }

  def blockUnitShutdown(): Unit = {
    if (server != null) {
      server.awaitTermination()
    }
  }

  private class UsersImpl extends UsersGrpc.Users {
  /* 中略 */
  }
}

というようなサーバを起動できるobjectを用意して,こいつを src/main/scala/grpc/server.scala みたいなところに保存しておく.

これで, $ sbt run すれば,このgrpcサーバが起動するわけだ.

こんなことをするようなサンプルを作っておいた.

github.com