Akka gRPC で Hello World

プログラミング言語として 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 を使って java をインストールします。

asdf plugin add java
asdf install java temurin-17.0.5+8

デフォルトでインストールしたバージョンの java が使用されるように設定します。

asdf global java temurin-17.0.5+8

scala

asdf を使って 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 のコードに変換されます。

SOASOAP の場合は 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/scalacom.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/scalacom.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"
}