プログラミング言語として Scala を使う場合、フレームワークとして Akka がよく利用されます。
また、gRPC の API は Protocol Buffers (protobuf) という Schema 定義言語で規定します。これは日本CTO協会が監修・編纂しているDX Criteriaで良いプラクティスとして SYSTEM-5-4 等でもあげられています。
この記事ではスキーマ駆動開発 (SDD) の実践として Akka gRPC を使った Hello World アプリケーションを実装します。
環境構築
開発と実行のため、次のツールのインストールが必要です。
- sbt 1.8.2
- scala 2.3.10
- grpcurl 1.8.7
私が使用している mac の場合のインストール手順をこれから説明します。
Homebrew がすでにインストールされているものとして説明します。
grpcurl
Homebrew を使って grpcurl をインストールします。
brew install grpcurl
asdf
Homebrew を使って asdf をインストールします。
brew install asdf
java
asdf plugin add java asdf install java temurin-17.0.5+8
デフォルトでインストールしたバージョンの java が使用されるように設定します。
asdf global java temurin-17.0.5+8
scala
asdf plugin add scala asdf install scala 2.13.10
デフォルトでインストールしたバージョンの scala が使用されるように設定します。
asdf global scala 2.3.10
sbt
asdf を使って sbt をインストールします。
asdf plugin add sbt asdf install sbt 1.8.2
デフォルトでインストールしたバージョンの sbt が使用されるように設定します。
asdf global sbt 1.8.2
プロジェクトの生成
sbt を使ってプロジェクトを生成します。
sbt new sbt/scala-seed.g8
プロジェクト名は hello-akka-grpc
です。
name [Scala Seed Project]: hello-akka-grpc
生成したプロジェクトは基本的な scala プロジェクトです。依存ライブラリや sbt plugin などの追加が必要です。
生成したプロジェクトに変更を加えたコードを GitHub リポジトリ に置いています。 ここからは、GitHub リポジトリのコードを使って説明します。
コード
gRPC は Protocol Buffers (protobuf) で定義され HTTP/2 で通信します。
protobuf の定義はさまざまな言語用のコードに変換できます。Akka gRPC の場合は、ScalaPB を使って scala のコードに変換されます。
SOA で SOAP の場合は XML、REST の場合は JSON、gRPC の場合は protobuf と理解すれば良さそうです。
では、早速 protobuf から記述します。src/main/protobuf/hello.proto
ファイルは次の通りです。
syntax = "proto3"; package com.pigumer; service HelloService { rpc Hello (HelloRequest) returns (HelloResult) {} } message HelloRequest { string name = 1; } message HelloResult { string value = 1; }
次に、sbt を使って scala コードを生成します。
sbt protocGenerate
target/scala-2.13/akka-grpc/main
に生成されたコードが出力されます。
サービスの実装
生成された HelloService
トレイトを拡張してサービスを実装します。src/main/scala
の com.pigumer
パッケージの HelloServiceImpl
クラスは次の通りです。
package com.pigumer import akka.stream.Materializer import scala.concurrent.Future class HelloServiceImpl(implicit mat: Materializer) extends HelloService { override def hello(in: HelloRequest): Future[HelloResult] = Future.successful(HelloResult(s"Hello ${ in.name }")) }
gRPC サーバを起動するためのコード
gRPC サーバを起動するコードも必要です。src/main/scala
の com.pigumer
パッケージの Main.scala
は次の通りです。
package com.pigumer import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.{ActorSystem, Behavior} import akka.http.scaladsl.Http import com.typesafe.config.ConfigFactory import scala.concurrent.Await import scala.concurrent.duration.Duration object Server { def setup: Behavior[Any] = Behaviors.setup { ctx => implicit val system = ctx.system val helloService = HelloServiceHandler.withServerReflection(new HelloServiceImpl()) Http(). newServerAt("127.0.0.1", -1). bind(helloService) Behaviors.same } } object Main extends App { val conf = ConfigFactory.load() .withFallback(ConfigFactory.defaultApplication()) val system = ActorSystem(Server.setup, "hello", conf) Await.ready(system.whenTerminated, Duration.Inf) }
Server
オブジェクトは、Akka Typed の作法でコーディングしています。
Main
オブジェクトは、ActorSystem が終了するのを待つように実装しています (Await.ready(system.whenTerminated, Duration.Inf)
)。
sbt run
を実行してください。
動作確認
grpcurl
を使って動作確認ができます。
まず、サービス一覧を取得してみましょう。
grpcurl -plaintext localhost:8080 list
次のレスポンスが返ります。
com.pigumer.HelloService grpc.reflection.v1alpha.ServerReflection
次に com.pigumer.HelloService
のメソッド一覧を取得します。
grpcurl -plaintext localhost:8080 list com.pigumer.HelloService
次のレスポンスが返ります。
com.pigumer.HelloService.Hello
最後に com.pigumer.HelloService.Hello
を実行します。protobuf で定義した通り、リクエストには name
が必要です。grpcurl は JSON 形式でリクエストを渡すことができます。
grpcurl -plaintext -d '{"name":"World"}' localhost:8080 com.pigumer.HelloService.Hello
次のレスポンスが返ります。
{ "value": "Hello World" }