Java で gRPC サーバーを書くことによって Java 知識をアップデートする (1)

Java を再びやることになったので、Java の知識をアップデートするために、gRPC サーバーを書いてみることにした。

* [はじめてのgRPC（Java）](https://kazuhira-r.hatenablog.com/entry/2018/11/04/230856)

こちらの記事が素晴らしいので、これを流しているだけなのだが、大量のヤクの毛刈りが発生しているので、それを含めて書いていきたい。

# maven でコンソールアプリケーションを書く時のテンプレート

最初に詰まったのが、`maven` でとても単純な、Hello World レベルのコンソールアプリケーションを作成すること。単純に、`maven-archetype-quickstart` を使うと良い。

```bash
mvn archetype:generate 
  -DgroupId=com.simplearchitect \
  -DartifactId=helloGRPC \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DinteractiveMode=false
```

# proto file

まさに Hello World レベルの protobuf.  `syntax` で `proto3` にしておかないと、古いバージョンが使われてしまう。`java_package` で、自動生成されるクラスの、パッケージが決まるので、もうちょっと真面目な名前にしておいてもよかったかもしれない。`outer_classname`はプロトコルの概念を表す自動生成クラスの名前になる。

下記のプロトコルバッファは、`SayHello` というメソッドを持ち、`HelloRequest` を送信すると、`HelloReply` が返ってくるシンプルなサービスを定義している。

_main/proto/helloworld.proto_

```proto
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
```

こちらを読むと、`proto` ファイルをよりよく理解できる。

* [Basics Tutorial](https://grpc.io/docs/languages/java/basics/)

# クラスの自動生成

`proto` ファイルを元にクラスを自動生成する必要がある。これは、`maven` に `dependency` と `plugin` を設定する必要がある。
[gRPC-Java - An RPC library and framework](https://github.com/grpc/grpc-java) のトップページを見て、`maven` の設定を行う。

`io.grpc` 系のライブラリの追加と、`protobuf-maven-plugin` および `os-maven-plugin` を入れる必要がある。ちなみに、`os-maven-plugin` は

> os-maven-plugin is a Maven extension/plugin that generates various useful platform-dependent project properties normalized from ${os.name} and ${os.arch}.

とのこと。[os-maven-plugin](https://github.com/trustin/os-maven-plugin).

```xml
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-netty-shaded</artifactId>
  <version>1.31.1</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-protobuf</artifactId>
  <version>1.31.1</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-stub</artifactId>
  <version>1.31.1</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
  <groupId>org.apache.tomcat</groupId>
  <artifactId>annotations-api</artifactId>
  <version>6.0.53</version>
  <scope>provided</scope>
</dependency>
　　：
<build>
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.6.2</version>
    </extension>
  </extensions>
  <plugins>
    <plugin>
      <groupId>org.xolstice.maven.plugins</groupId>
      <artifactId>protobuf-maven-plugin</artifactId>
      <version>0.6.1</version>
      <configuration>
        <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.31.1:exe:${os.detected.classifier}</pluginArtifact>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
```

この設定により、`mvn clean package` で、自動でクラスが生成されるようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/89731534-d9e7-fd7a-0bfb-e4dbb835862e.png)

ちなみに IntelliJ にプロジェクトを作成する場合は、一旦 `target` を生成したのちに実施するほうがよさそうだ。デフォルトのままだと、`target` ディレクトリは表示されなくなってしまう。`target` を作った後に、プロジェクトをintelliJ に読み込むと、自動で設定してくれる。そうでない場合は下記の設定 File > Project Structure Module を設定する必要がある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/42f7768f-beb4-5d31-6d8f-0390c410e633.png)

* [Show 'target' or build output in Project or Commander windows](https://intellij-support.jetbrains.com/hc/en-us/community/posts/206938305-Show-target-or-build-output-in-Project-or-Commander-windows)

尚、IntelliJ にロードするときは、プロジェクトを右クリックして、Mavenプロジェクトとして追加的なものを選んでおかないと、`maven` ではエラーは出なくとも、画面上 dependency が解決できず Error になってしまう。

# gRPC サーバーを書く

生成されたクラスを参照してサーバーを書く。これを Main のプログラムから実行できるようにする。単純に、サーバーを起動して、設定して、継承したクラスをセットするだけになっている。

```java
package com.simplearchitect;

import java.io.IOException;
import java.util.logging.Logger;

import io.grpc.BindableService;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.GreeterGrpc.GreeterImplBase;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.stub.CallStreamObserver;

import static io.grpc.ServerBuilder.*;

public class SimpleServer {
    Logger logger = Logger.getLogger(getClass().getName());

    Server server;

    public void start() throws IOException {
        server = ServerBuilder
                .forPort(8080)
                .addService((BindableService) new SimpleHelloServiceImpl())
                .build()
                .start();
        logger.info("start gRPC server.");
    }

    public void stop() {
        if (server != null) {
            server.shutdown();
            logger.info("Shutting down gRPC server.");
        }
    }

    public void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    public static class SimpleHelloServiceImpl extends GreeterImplBase {
        Logger logger = Logger.getLogger(getClass().getName());

        public void sayHello(HelloRequest request, CallStreamObserver<HelloReply> responseObserver) {
            logger.info(String.format("request: name = %s", request.getName()));
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello, " + request.getName()).build();
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
        }
    }
}

```

# abstract static class
不思議なことに、static クラスを new している。これはどういうことだろうか？ 下記のクラスが継承元のクラスだが、`static abstract class` になっている。これは、実際は、`static` クラスではなく、このクラスは、インナークラスになっており、インナークラスの親のクラスをインスタンス化していなくても、インスタンス化できることを示すため、`static` がついているので、実態はインスタンス化できるクラスである。

```java
public static abstract class GreeterImplBase implements io.grpc.BindableService {

    /**
     * <pre>
     * Sends a greeting
     * </pre>
     */
    public void sayHello(io.grpc.examples.helloworld.HelloRequest request,
        io.grpc.stub.StreamObserver<io.grpc.examples.helloworld.HelloReply> responseObserver) {
      asyncUnimplementedUnaryCall(getSayHelloMethod(), responseObserver);
    }

    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
          .addMethod(
            getSayHelloMethod(),
            asyncUnaryCall(
              new MethodHandlers<
                io.grpc.examples.helloworld.HelloRequest,
                io.grpc.examples.helloworld.HelloReply>(
                  this, METHODID_SAY_HELLO)))
          .build();
    }
  }
```

# Java への Dependency の追加

`mvn clean package` で jar ファイルを作って実行するとエラーになる。Jar にライブラリが含まれていないためだ。`maven-assembly-plugin` を追加しよう。これで、`mainClass` も指定できて、依存関係にある jar を含んだライブラリを作ってくれる。
`

```xml
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.1.0</version>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>com.simplearchitect.App</mainClass>
            </manifest>
          </archive>
        </configuration>
        <executions>
          <execution>
            <id>sample</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

```

* [Apache Maven Assembly Plugin](https://maven.apache.org/plugins/maven-assembly-plugin/)

> The Assembly Plugin for Maven enables developers to combine project output into a single distributable archive that also contains dependencies, modules, site documentation, and other files.

うむ。まさに求めていたもの。

# 実行

```powershell
PS > > java -jar .\target\helloGRPC-1.0-SNAPSHOT-jar-with-dependencies.jar
Aug 16, 2020 11:17:33 PM com.simplearchitect.SimpleServer start
INFO: start gRPC server.
> Enter stop.
```

ちゃんと動くかしらんけど、まずは、コンパイルされて、ライブラリ付きの jar ができて、動くところまでは出来た。次回はクライアントを作ってみて、動かしてみる。`CallStreamObserver` の挙動を明確に理解したい。