クライアント側でのgRPC

ほとんどのシステム間通信コンポーネントでは、RESTを使用する場合、JSONでペイロードをシリアル化します。現時点では、JSONには広く使用されているスキーマ検証標準が欠けています:JSON Schemaが広く普及していません。標準的なスキーマ検証を使用することで、検証を第三者ライブラリに委譲し、それで完了することができます。それがない場合、コード内で手動検証に頼らざるを得ません。さらに悪いことに、検証コードをスキーマと同期させる必要があります。

一方、XMLには標準装備でスキーマ検証があります:XMLドキュメントは、それが準拠しなければならない文法を宣言することができます。SOAPはXMLをベースにしているため、その恩恵を受けています。

他のシリアル化代替手段には、スキーマ検証オプションがあります:例えば、AvroKryo、そしてProtocol Buffersです。興味深いことに、gRPCはProtobufを使用して分散コンポーネント間でRPCを提供しています:

gRPCは、あらゆる環境で実行できる現代のオープンソース高性能なリモートプロシージャコール(RPC)フレームワークです。負荷分散、トレーシング、ヘルスチェック、認証のためのプラグインサポートを含め、データセンタ内およびデータセンタ間のサービスを効率的に接続できます。また、デバイス、モバイルアプリケーション、ブラウザからバックエンドサービスへの分散計算の最後のマイルにも適用可能です。

なぜgRPCなのか?

さらに、Protocolはバイナリシリアル化メカニズムであり、多くの帯域幅を節約します。したがって、gRPCはシステム間通信に優れたオプションです。しかし、すべてのコンポーネントがgRPCで通信する場合、単純なクライアントはどのようにそれらを呼び出すことができますか?この記事では、gRPCサービスを構築し、cURLからそれを呼び出す方法を示します。

A Simple gRPC Service

gRPCドキュメントは詳細ですので、概要を示します。

  • gRPCはリモートプロシージャコールフレームワークです。
  • さまざまな言語で動作します。
  • Protocol Buffersに依存しています。
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

  • CNCFのポートフォリオの一部であり、現在はインキュベーション段階にあります。

gRPCサービスを設定しましょう。Java、Kotlin、Spring Boot、および専用のgRPC Spring Boot統合プロジェクトを使用します。プロジェクト構造は、モデル用とコード用の2つのプロジェクトを保持します。モデルプロジェクトから始めましょう。

I didn’t want something complicated. Reusing a simple example is enough: the request sends a string, and the response prefixes it with Hello. We design this model in a dedicated Protobuf schema file:

ProtoBuf

 

syntax = "proto3";                                        //1

package ch.frankel.blog.grpc.model;                       //2

option java_multiple_files = true;                        //3
option java_package = "ch.frankel.blog.grpc.model";       //3
option java_outer_classname = "HelloProtos";              //3

service HelloService {                                    //4
    rpc SayHello (HelloRequest) returns (HelloResponse) {
    }
}

message HelloRequest {                                    //5
    string name = 1;                                      //6
}

message HelloResponse {                                   //7
    string message = 1;                                   //6
}

  1. Protobuf定義バージョン

  2. パッケージ
  3. Java固有の設定
  4. サービス定義
  5. リクエスト定義
  6. フィールド定義:最初にタイプ、次に名前、最後に順序が来ます。
  7. レスポンス定義

Mavenを使用してJavaのスケルトンコードを生成します。

XML

 

<project>
  <dependencies>
    <dependency>
      <groupId>io.grpc</groupId>                         <!--1-->
      <artifactId>grpc-stub</artifactId>
      <version>${grpc.version}</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>                         <!--1-->
      <artifactId>grpc-protobuf</artifactId>
      <version>${grpc.version}</version>
    </dependency>
    <dependency>
      <groupId>jakarta.annotation</groupId>              <!--1-->
      <artifactId>jakarta.annotation-api</artifactId>
      <version>1.3.5</version>
      <optional>true</optional>
    </dependency>
  </dependencies>
  <build>
    <extensions>
      <extension>
        <groupId>kr.motd.maven</groupId>                 <!--2-->
        <artifactId>os-maven-plugin</artifactId>
        <version>1.7.1</version>
      </extension>
    </extensions>
    <plugins>
      <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>    <!--3-->
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>${protobuf-plugin.version}</version>
        <configuration>
          <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
          <pluginId>grpc-java</pluginId>
          <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

  1. コンパイル時の依存関係
  2. 次のプラグインで使用するためのオペレーティングシステムの情報をスニッフィングします。
  3. protoファイルからJavaコードを生成します。

コンパイル後、構造は次のようになるはずです。

クラスをJARにパッケージ化し、ウェブアプリプロジェクトで使用できます。後者はKotlinですが、それが私のお気に入りのJVM言語だからです。

gRPCエンドポイントをSpring Bootと統合するために必要なのは、特定のSpring Bootスタータ依存関係だけです。

XML

 

<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-server-spring-boot-starter</artifactId>
  <version>2.14.0.RELEASE</version>
</dependency>

重要な部分はこちら。

Kotlin

 

@GrpcService                                                        //1
class HelloService : HelloServiceImplBase() {                       //2
  override fun sayHello(
      request: HelloRequest,                                        //2
      observer: StreamObserver<HelloResponse>                       //3
  ) {
    with(observer) {
      val reply = HelloResponse.newBuilder()                        //2
                               .setMessage("Hello ${request.name}") //4
                               .build()
      onNext(reply)                                                 //5
      onCompleted()                                                 //5
    }
  }
}

  1. grpc-server-spring-boot-starterがアノテーションを検出し、その魔法をかけます。
  2. 上記のプロジェクトで生成された参照クラス
  3. メソッドシグネチャはStreamObserverパラメータを許可します。このクラスはgrpc-stub.jarから来ています。
  4. リクエストを取得し、レスポンスメッセージを構築するためにプレフィックスを付けます。
  5. イベントを再生します。

これで./mvnw spring-boot:runでウェブアプリを起動できます。

gRPCサービスのテスト

投稿の全体的な考え方は、通常のツールでgRPCサービスにアクセスすることは不可能だということです。テストするためには専用のツールが必要です。私はgrpcurlを見つけました。インストールして利用可能なサービスをリストアップしましょう。

Shell

 

grpcurl --plaintext localhost:9090 list   #1-2

  1. TLS検証なしで利用可能なすべてのgRPCサービスをリストアップします。
  2. gRPCと他のチャネル(例えばREST)との衝突を避けるため、Spring Bootは別のポートを使用します。
Plain Text

 

ch.frankel.blog.grpc.model.HelloService   #1
grpc.health.v1.Health                     #2
grpc.reflection.v1alpha.ServerReflection  #2

  1. 定義したgRPCサービス
  2. カスタムスタータによって提供される2つの追加サービス

また、サービスの構造についても詳しく見ていくことができます。

Shell

 

grpcurl --plaintext localhost:9090 describe ch.frankel.blog.grpc.model.HelloService
Java

 

service HelloService {
  rpc SayHello ( .ch.frankel.blog.grpc.model.HelloRequest ) returns ( .ch.frankel.blog.grpc.model.HelloResponse );
}

最後に、データでサービスを呼び出すことができます:

Shell

 

grpcurl --plaintext -d '{"name": "John"}' localhost:9090 ch.frankel.blog.grpc.model.HelloService/SayHello
JSON

 

{
  "message": "Hello John"
}

gRPCサービスを通常のツールでアクセスする

gRPCサービスにアクセスする必要がある通常のJavaScriptクライアントサイドアプリケーションがあるとします。どのような代替案があるでしょうか?

一般的なアプローチはgrpc-webを通じて行われます:

A JavaScript implementation of gRPC for browser clients. For more information, including a quick start, see the gRPC-web documentation.

gRPC-webクライアントは、特別なプロキシを介してgRPCサービスに接続します。デフォルトでは、gRPC-webはEnvoyを使用します。

将来的には、Python、Java、Nodeなどの言語固有のWebフレームワークでgRPC-webがサポートされることが期待されます。詳細については、ロードマップを参照してください。

grpc-web

説明によれば、現時点ではJavaScriptのみを対象としているという1つの制限があります。しかし、もう1つあります。かなり侵入的です。protoファイルを取得し、ボイラープレートコードを生成し、コードからそれを呼び出す必要があります。各クライアントタイプについてそれを行う必要があります。さらに悪いことに、protoファイルが変更された場合、それぞれのクライアントコードを再生成する必要があります。

ただし、APIゲートウェイを使用している場合は代替案が存在します。Apache APISIXでそれを行う方法を説明しますが、他のゲートウェイも同様のことができるかもしれません。grpc-transcodeは、REST呼び出しをgRPCに変換して逆に戻すプラグインです。

最初のステップは、Apache APISIXにprotoファイルを登録することです。

Shell

 

curl http://localhost:9180/apisix/admin/protos/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d "{ \"content\": \"$(sed 's/"/\\"/g' ../model/src/main/proto/model.proto)\" }"

第二ステップは、上記のプラグインを使用してルートを作成することです:

Shell

 

curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
  "uri": "/helloservice/sayhello",                           #1
  "plugins": {
    "grpc-transcode": {
      "proto_id": "1",                                       #2
      "service": "ch.frankel.blog.grpc.model.HelloService",  #3
      "method": "SayHello"                                   #4
    }
  },
  "upstream": {
    "scheme": "grpc",
    "nodes": {
      "server:9090": 1
    }
  }
}'

  1. 粒度の細かいルートを定義します。
  2. 前のコマンドで定義されたprotoファイルを参照します。
  3. gRPCサービス
  4. gRPCメソッド

この時点で、任意のクライアントは定義されたエンドポイントにHTTPリクエストを行うことができます。Apache APISIXは呼び出しをgRPCにトランスコードし、定義されたサービスに転送し、応答を取得し、再びトランスコードします。

Shell

 

curl localhost:9080/helloservice/sayhello?name=John
JSON

 

{"message":"Hello John"}

比較のためにgrpc-web、APIゲートウェイアプローチにより、protoファイルをゲートウェイ自体という単一のコンポーネントで共有できます。

トランスコーディングの利点

この時点で、APIゲートウェイの機能を活用できます。nameが渡されていない場合にデフォルト値を設定したいとします。例えば、Worldです。開発者はそれをコード内に設定することを喜んで行いますが、値の変更には完全なビルドとデプロイが必要です。デフォルト値をゲートウェイのルート処理チェーンに配置すれば、変更はほぼ即時に行えます。ルートを次のように変更しましょう:

Shell

 

curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
  "uri": "/helloservice/sayhello",
  "plugins": {
    "grpc-transcode": {
      ...
    },
    "serverless-pre-function": {                    #1
      "phase": "rewrite",                           #2
      "functions" : [
        "return function(conf, ctx)                 #3
          local core = require(\"apisix.core\")
          if not ngx.var.arg_name then
            local uri_args = core.request.get_uri_args(ctx)
            uri_args.name = \"World\"
            ngx.req.set_uri_args(uri_args)
          end
        end"
      ]
    }
  },
  "upstream": {
      ...
  }
}'

  1. どれにも当てはまらない一般的な汎用プラグイン
  2. リクエストを書き換えます。
  3. このトリックを実行する魔法のLuaコード

これで、引数が空のリクエストを実行し、予想される結果を得ることができます。

Shell

 

curl localhost:9080/helloservice/sayhello?name
JSON

 

{"message":"Hello World"}

結論

この投稿では、gRPCについて簡単に説明し、サービス間通信にどのように役立つかを説明しました。Spring Bootとgrpc-server-spring-boot-starterを使用してシンプルなgRPCサービスを開発しました。ただし、コストがかかります。通常のクライアントはサービスにアクセスできません。テストするためにgrpcurlに頼らなければなりませんでした。JavaScriptベースのクライアントやブラウザも同様です。

この制限を回避するために、API Gatewayを活用できます。Apache APISIXをgrpc-transcodeプラグインで構成する方法をデモンストレーションしました。目的の結果を得るために。

この投稿の完全なソースコードはGitHubで見つけることができます。

さらに進む

Source:
https://dzone.com/articles/grpc-on-the-client-side