水曜日, 6月 18, 2025
- Advertisment -
ホームニューステックニュース【grpc】option java_generic_services = true; が非推奨の理由とは? #Java

【grpc】option java_generic_services = true; が非推奨の理由とは? #Java



【grpc】option java_generic_services = true; が非推奨の理由とは? #Java

gRPCを勉強している際にoption java_generic_services = trueが非推奨になっているが理由が不明だった。 そこで、option java_generic_servicestruefalse(default)の時で、スタブにどのような違いが出てくるかについて調査した時の記録を紹介するものである

調査記録を紹介するにあたって、以下の流れで進めていく

  1. grpcの利用する流れを紹介
  2. ↑の具体的なサンプルを紹介
  3. 具体的なサンプルのjava_generic_servicesをtrueにしてみてスタブにどのような違いが出るかを見てみる
  • Google が開発した RPC ( Remote Procedure Call )システム
  • Protocol Buffers と呼ばれるインタフェース記述言語(IDL)でインタフェースを定義する
  • バイナリファイルでデータの送受信がされるためRESTなどで用いられるjsonに比べて軽量である
  • 様々な言語に変換可能である
  1. .protoファイルにデータの定義を行う
  2. protocを用いて特定のプログラミング言語に応じてスタブを作成する
  3. 作成したスタブを用いて、指定した開発言語のオブジェクトを生成する
    1. サーバープログラムを作成する
    2. クライアントプログラムを作成する
  4. ↑のプログラムを実行する

今回は、option java_generic_services = true;を説明するのが目的のため、クライアントプログラムは、grpcで代用する
なので、作成するファイルは以下の3つになっている

  1. EchoService.proto
  2. EchoServer.java
  3. pom.xml

また、ディレクトリ構成は以下のようになる。

.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── grpc
        │               └── server
        │                   └── EchoServer.java
        └── proto
            └── EchoService.proto

.protoファイルにデータの定義を行う

EchoService.proto

syntax = "proto3";

package com.example.grpc;

option java_multiple_files = true;

message EchoRequest {
    string message = 1;
}

message EchoResponse {
    string message = 1;
    string from = 2;
}

service EchoService {
    rpc echo(EchoRequest) returns (EchoResponse);
}

protocを用いて特定のプログラミング言語に応じてスタブを作成する

Mavenでスタブを作成する。
generate-sources フェーズで protoc が実行される

ちなみに pom.xmlは以下のように記述している

pom.xml

pom.xml



 xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    4.0.0
    echo-server

    1.0-SNAPSHOT
    com.example.grpc
    jar

    
        UTF-8
        17
        17
        1.64.0
        3.25.3
    

    
        
            io.grpc
            grpc-protobuf
            ${grpc.version}
        
        
            io.grpc
            grpc-netty
            ${grpc.version}
        
        
            io.grpc
            grpc-stub
            ${grpc.version}
        
        
            com.google.protobuf
            protobuf-java
            ${protobuf.version}
        
        
            javax.annotation
            javax.annotation-api
            1.3.2
            provided
        
    

    
        echo-server
        
            
                kr.motd.maven
                os-maven-plugin
                1.7.0
            
        
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.11.0
                
                    ${maven.compiler.source}
                    ${maven.compiler.target}
                
            
            
            
                org.xolstice.maven.plugins
                protobuf-maven-plugin
                0.6.1
                
                    com.google.protobuf:protoc:3.25.3:exe:${os.detected.classifier}
                    grpc-java
                    io.grpc:protoc-gen-grpc-java:1.64.0:exe:${os.detected.classifier}
                
                
                    
                        
                            compile
                            compile-custom
                        
                    
                
            
            
            
                org.apache.maven.plugins
                maven-shade-plugin
                3.6.0
                
                    
                        package
                        
                            shade
                        
                        
                            false
                            
                                 implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    com.example.grpc.server.EchoServer
                                
                                 implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                                 implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    META-INF/io.netty.versions.properties
                                
                            
                        
                    
                
            
        
    


作成したスタブを用いて、指定した開発言語のオブジェクトを生成する

サーバープログラム

package com.example.grpc.server;

import com.example.grpc.EchoRequest;
import com.example.grpc.EchoResponse;
import com.example.grpc.EchoServiceGrpc;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class EchoServer {
  static public void main(String[] args) throws IOException, InterruptedException {

    Server server = ServerBuilder.forPort(8080)
        .addService(new EchoServiceImpl()).build();

    System.out.println("Starting server...");
    server.start();
    System.out.println("Server started!");
    server.awaitTermination();
  }
}

// protoから作成したスタブを利用してサーバープログラムを実装
class EchoServiceImpl extends EchoServiceGrpc.EchoServiceImplBase {

  @Override
  public void echo(EchoRequest request, StreamObserverEchoResponse> responseObserver) {
    try {
      String from = InetAddress.getLocalHost().getHostAddress();
      System.out.println("Received: " + request.getMessage());
      responseObserver.onNext(EchoResponse.newBuilder()
          .setFrom(from)
          .setMessage(request.getMessage())
          .build());
      responseObserver.onCompleted();
    } catch (UnknownHostException e) {
      responseObserver.onError(e);
    }
  }
}

クライアントプログラム

(再掲)今回は、grpcurlで代用するので省略

↑のプログラムを実行する

$ java -jar target/echo-server.jar &
$ grpcurl --plaintext \
    -d '{"message": "hello world!"}' \
    -proto src/main/proto/EchoService.proto \
    localhost:8080 com.example.grpc.EchoService.echo
{
  "message": "hello world!",
  "from": "127.0.0.1"
}

とりあえずプログラムは実行できたので、protoにoption java_generic_services = true; を追加して、スタブにどのような変化があるかをみていく

まずは、option java_generic_services = true;なしの状態でスタブを作成する
そして、差分を見やすくするためにgitで管理する

$ mvn clean package
$ git init
$ git add -A
$ git commit -m "first commit"

option java_generic_services = true;をつける

EchoService.proto

syntax = "proto3";

package com.example.grpc;

option java_multiple_files = true;
+ option java_generic_services = true;

message EchoRequest {
    string message = 1;
}

message EchoResponse {
    string message = 1;
    string from = 2;
}

service EchoService {
    rpc echo(EchoRequest) returns (EchoResponse);
}
$ mvn clean package
$ git add -A
$ git commit -m "modify proto"
$ git status  target/generated-sources/protobuf                                        
On branch master
Changes to be committed:
  (use "git restore --staged ..." to unstage)
        new file:   target/generated-sources/protobuf/java/com/example/grpc/EchoService.java
        modified:   target/generated-sources/protobuf/java/com/example/grpc/EchoServiceOuterClass.java

target/generated-sources/protobufの配下を見てみると、

  • EchoService.javaが新規作成されて
  • EchoServiceOuterClass.javaが変更されていた

EchoService.javaを見てみる

EchoService.java全文
// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: EchoService.proto

// Protobuf Java Version: 3.25.3
package com.example.grpc;

/**
 * Protobuf service {@code com.example.grpc.EchoService}
 */
public  abstract class EchoService
    implements com.google.protobuf.Service {
  protected EchoService() {}

  public interface Interface {
    /**
     * rpc echo(.com.example.grpc.EchoRequest) returns (.com.example.grpc.EchoResponse);
     */
    public abstract void echo(
        com.google.protobuf.RpcController controller,
        com.example.grpc.EchoRequest request,
        com.google.protobuf.RpcCallbackcom.example.grpc.EchoResponse> done);

  }

  public static com.google.protobuf.Service newReflectiveService(
      final Interface impl) {
    return new EchoService() {
      @java.lang.Override
      public  void echo(
          com.google.protobuf.RpcController controller,
          com.example.grpc.EchoRequest request,
          com.google.protobuf.RpcCallbackcom.example.grpc.EchoResponse> done) {
        impl.echo(controller, request, done);
      }

    };
  }

  public static com.google.protobuf.BlockingService
      newReflectiveBlockingService(final BlockingInterface impl) {
    return new com.google.protobuf.BlockingService() {
      public final com.google.protobuf.Descriptors.ServiceDescriptor
          getDescriptorForType() {
        return getDescriptor();
      }

      public final com.google.protobuf.Message callBlockingMethod(
          com.google.protobuf.Descriptors.MethodDescriptor method,
          com.google.protobuf.RpcController controller,
          com.google.protobuf.Message request)
          throws com.google.protobuf.ServiceException {
        if (method.getService() != getDescriptor()) {
          throw new java.lang.IllegalArgumentException(
            "Service.callBlockingMethod() given method descriptor for " +
            "wrong service type.");
        }
        switch(method.getIndex()) {
          case 0:
            return impl.echo(controller, (com.example.grpc.EchoRequest)request);
          default:
            throw new java.lang.AssertionError("Can't get here.");
        }
      }

      public final com.google.protobuf.Message
          getRequestPrototype(
          com.google.protobuf.Descriptors.MethodDescriptor method) {
        if (method.getService() != getDescriptor()) {
          throw new java.lang.IllegalArgumentException(
            "Service.getRequestPrototype() given method " +
            "descriptor for wrong service type.");
        }
        switch(method.getIndex()) {
          case 0:
            return com.example.grpc.EchoRequest.getDefaultInstance();
          default:
            throw new java.lang.AssertionError("Can't get here.");
        }
      }

      public final com.google.protobuf.Message
          getResponsePrototype(
          com.google.protobuf.Descriptors.MethodDescriptor method) {
        if (method.getService() != getDescriptor()) {
          throw new java.lang.IllegalArgumentException(
            "Service.getResponsePrototype() given method " +
            "descriptor for wrong service type.");
        }
        switch(method.getIndex()) {
          case 0:
            return com.example.grpc.EchoResponse.getDefaultInstance();
          default:
            throw new java.lang.AssertionError("Can't get here.");
        }
      }

    };
  }

  /**
   * rpc echo(.com.example.grpc.EchoRequest) returns (.com.example.grpc.EchoResponse);
   */
  public abstract void echo(
      com.google.protobuf.RpcController controller,
      com.example.grpc.EchoRequest request,
      com.google.protobuf.RpcCallbackcom.example.grpc.EchoResponse> done);

  public static final
      com.google.protobuf.Descriptors.ServiceDescriptor
      getDescriptor() {
    return com.example.grpc.EchoServiceOuterClass.getDescriptor().getServices().get(0);
  }
  public final com.google.protobuf.Descriptors.ServiceDescriptor
      getDescriptorForType() {
    return getDescriptor();
  }

  public final void callMethod(
      com.google.protobuf.Descriptors.MethodDescriptor method,
      com.google.protobuf.RpcController controller,
      com.google.protobuf.Message request,
      com.google.protobuf.RpcCallback
        com.google.protobuf.Message> done) {
    if (method.getService() != getDescriptor()) {
      throw new java.lang.IllegalArgumentException(
        "Service.callMethod() given method descriptor for wrong " +
        "service type.");
    }
    switch(method.getIndex()) {
      case 0:
        this.echo(controller, (com.example.grpc.EchoRequest)request,
          com.google.protobuf.RpcUtil.com.example.grpc.EchoResponse>specializeCallback(
            done));
        return;
      default:
        throw new java.lang.AssertionError("Can't get here.");
    }
  }

  public final com.google.protobuf.Message
      getRequestPrototype(
      com.google.protobuf.Descriptors.MethodDescriptor method) {
    if (method.getService() != getDescriptor()) {
      throw new java.lang.IllegalArgumentException(
        "Service.getRequestPrototype() given method " +
        "descriptor for wrong service type.");
    }
    switch(method.getIndex()) {
      case 0:
        return com.example.grpc.EchoRequest.getDefaultInstance();
      default:
        throw new java.lang.AssertionError("Can't get here.");
    }
  }

  public final com.google.protobuf.Message
      getResponsePrototype(
      com.google.protobuf.Descriptors.MethodDescriptor method) {
    if (method.getService() != getDescriptor()) {
      throw new java.lang.IllegalArgumentException(
        "Service.getResponsePrototype() given method " +
        "descriptor for wrong service type.");
    }
    switch(method.getIndex()) {
      case 0:
        return com.example.grpc.EchoResponse.getDefaultInstance();
      default:
        throw new java.lang.AssertionError("Can't get here.");
    }
  }

  public static Stub newStub(
      com.google.protobuf.RpcChannel channel) {
    return new Stub(channel);
  }

  public static final class Stub extends com.example.grpc.EchoService implements Interface {
    private Stub(com.google.protobuf.RpcChannel channel) {
      this.channel = channel;
    }

    private final com.google.protobuf.RpcChannel channel;

    public com.google.protobuf.RpcChannel getChannel() {
      return channel;
    }

    public  void echo(
        com.google.protobuf.RpcController controller,
        com.example.grpc.EchoRequest request,
        com.google.protobuf.RpcCallbackcom.example.grpc.EchoResponse> done) {
      channel.callMethod(
        getDescriptor().getMethods().get(0),
        controller,
        request,
        com.example.grpc.EchoResponse.getDefaultInstance(),
        com.google.protobuf.RpcUtil.generalizeCallback(
          done,
          com.example.grpc.EchoResponse.class,
          com.example.grpc.EchoResponse.getDefaultInstance()));
    }
  }

  public static BlockingInterface newBlockingStub(
      com.google.protobuf.BlockingRpcChannel channel) {
    return new BlockingStub(channel);
  }

  public interface BlockingInterface {
    public com.example.grpc.EchoResponse echo(
        com.google.protobuf.RpcController controller,
        com.example.grpc.EchoRequest request)
        throws com.google.protobuf.ServiceException;
  }

  private static final class BlockingStub implements BlockingInterface {
    private BlockingStub(com.google.protobuf.BlockingRpcChannel channel) {
      this.channel = channel;
    }

    private final com.google.protobuf.BlockingRpcChannel channel;

    public com.example.grpc.EchoResponse echo(
        com.google.protobuf.RpcController controller,
        com.example.grpc.EchoRequest request)
        throws com.google.protobuf.ServiceException {
      return (com.example.grpc.EchoResponse) channel.callBlockingMethod(
        getDescriptor().getMethods().get(0),
        controller,
        request,
        com.example.grpc.EchoResponse.getDefaultInstance());
    }

  }

  // @@protoc_insertion_point(class_scope:com.example.grpc.EchoService)
}

Starting with version 2.3.0, RPC implementations should not try to build on this, but should instead provide code generator plugins which generate code specific to the particular RPC implementation.
com.google.protobuf Interface Service

RPC 2.3.0以降はこのinterfaceを実装するのではなく、言語に応じてスタブを生成するプラグインを利用するのが推奨になった。
つまり、このファイル(EchoServiece)は、RPC 2.2.xまでで利用されていた形式。

詳細は割愛するが、この汎用APIの実装方式の場合はgRPCと比較して以下のような違いがある

観点 旧方式(汎用RPC) 新方式(gRPCで主に利用)
コネクション管理・再試行 利用者が RpcChannel などで実装 ManagedChannelCallOptions に組み込まれており、再試行やタイムアウトも設定可能
ロードバランシング & 名前解決 外部ロードバランサや独自実装に依存 PickFirstRoundRobinxDS による動的構成が gRPC 標準でサポートされている
複数の言語での通信方法 各言語ごとに RpcChannel 相当の実装が必要 .proto から各言語に対応したスタブを自動生成(公式サポート:Java、Go、Python、C++ など)

EchoServiceOuterClass.javaを見てみる

差分は以下のような感じ

$ git diff HEAD target/generated-sources/protobuf/java/com/example/grpc/EchoServiceOuterClass.java             
diff --git a/target/generated-sources/protobuf/java/com/example/grpc/EchoServiceOuterClass.java b/target/generated-sources/protobuf/java/com/example/grpc/EchoServiceOuterClass.java
index 7d88860..94be40b 100644
--- a/target/generated-sources/protobuf/java/com/example/grpc/EchoServiceOuterClass.java
+++ b/target/generated-sources/protobuf/java/com/example/grpc/EchoServiceOuterClass.java
@@ -39,7 +39,7 @@ public final class EchoServiceOuterClass {
       "ponse\022\017\n\007message\030\001 \001(\t\022\014\n\004from\030\002 \001(\t2T\n\013" +
       "EchoService\022E\n\004echo\022\035.com.example.grpc.E" +
       "choRequest\032\036.com.example.grpc.EchoRespon" +
-      "seB\002P\001b\006proto3"
+      "seB\005P\001\210\001\001b\006proto3"
     };
     descriptor = com.google.protobuf.Descriptors.FileDescriptor
       .internalBuildGeneratedFileFrom(descriptorData,

変更後のファイルは以下のようになっている

  private static  com.google.protobuf.Descriptors.FileDescriptor
      descriptor;
  static {
    java.lang.String[] descriptorData = {
      "\n\021EchoService.proto\022\020com.example.grpc\"\036\n" +
      "\013EchoRequest\022\017\n\007message\030\001 \001(\t\"-\n\014EchoRes" +
      "ponse\022\017\n\007message\030\001 \001(\t\022\014\n\004from\030\002 \001(\t2T\n\013" +
      "EchoService\022E\n\004echo\022\035.com.example.grpc.E" +
      "choRequest\032\036.com.example.grpc.EchoRespon" +
      "seB\005P\001\210\001\001b\006proto3"
    };
    descriptor = com.google.protobuf.Descriptors.FileDescriptor
      .internalBuildGeneratedFileFrom(descriptorData,
        new com.google.protobuf.Descriptors.FileDescriptor[] {
        });

上記のコードのクラスは、FileDescriptorに変換してから、FileDescriptor#toProtoで差分を比較してみると、java_generic_servicesのみが有効になっていることがわかった。

FileDescriptor#toProtoで差分を比較する
import com.google.protobuf.Descriptors.FileDescriptor;

public class ShowFileOptions {
  public static void main(String[] args) {

      String[] descriptorData = {
      "\n\021EchoService.proto\022\020com.example.grpc\"\036\n" +
      "\013EchoRequest\022\017\n\007message\030\001 \001(\t\"-\n\014EchoRes" +
      "ponse\022\017\n\007message\030\001 \001(\t\022\014\n\004from\030\002 \001(\t2T\n\013" +
      "EchoService\022E\n\004echo\022\035.com.example.grpc.E" +
      "choRequest\032\036.com.example.grpc.EchoRespon" +
      "seB\002P\001b\006proto3"
    };

      String[] descriptorDataWithOption = {
      "\n\021EchoService.proto\022\020com.example.grpc\"\036\n" +
      "\013EchoRequest\022\017\n\007message\030\001 \001(\t\"-\n\014EchoRes" +
      "ponse\022\017\n\007message\030\001 \001(\t\022\014\n\004from\030\002 \001(\t2T\n\013" +
      "EchoService\022E\n\004echo\022\035.com.example.grpc.E" +
      "choRequest\032\036.com.example.grpc.EchoRespon" +
      "seB\005P\001\210\001\001b\006proto3"
    };

    FileDescriptor descriptor = com.google.protobuf.Descriptors.FileDescriptor
      .internalBuildGeneratedFileFrom(descriptorData,
        new com.google.protobuf.Descriptors.FileDescriptor[] {
        });

    FileDescriptor descriptorWithOption = com.google.protobuf.Descriptors.FileDescriptor
      .internalBuildGeneratedFileFrom(descriptorDataWithOption,
        new com.google.protobuf.Descriptors.FileDescriptor[] {
        });


    System.out.println(descriptorWithOption.toProto());
  }
}
name: "EchoService.proto"
package: "com.example.grpc"
message_type {
  name: "EchoRequest"
  field {
    name: "message"
    number: 1
    label: LABEL_OPTIONAL
    type: TYPE_STRING
  }
}
message_type {
  name: "EchoResponse"
  field {
    name: "message"
    number: 1
    label: LABEL_OPTIONAL
    type: TYPE_STRING
  }
  field {
    name: "from"
    number: 2
    label: LABEL_OPTIONAL
    type: TYPE_STRING
  }
}
service {
  name: "EchoService"
  method {
    name: "echo"
    input_type: ".com.example.grpc.EchoRequest"
    output_type: ".com.example.grpc.EchoResponse"
  }
}
options {
  java_multiple_files: true
+ java_generic_services: true
}
syntax: "proto3"
  • option java_generic_services = true;は、gRPC Java がまだ正式リリース前だった時代の当時は protoc-gen-grpc-java が無く、「Protobuf の標準 Service API(汎用サービス API)で RPC を書く」方式だった
  • その後、gRPC 公式の Java プラグイン(protoc-gen-grpc-java)が登場したため、汎用サービス APIで書く必要は無くなった
  • なので、汎用サービス APIを使わない場合はjava_generic_servicesで生成されるインタフェースはほとんど利用されず、コードベースに余計なファイルを増やすだけになってしまう(不要なファイルが増える)
  • また、汎用サービスAPIを利用する場合は、自前で実装する場合が多くかなり使いずらい(grpcの場合はライブラリに任せられる)





Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -