SpringBootでgRPCやってみた

SpringBootでgRPCの初期設定を行った際のメモ

```build.gradle
// プラグイン追加
apply plugin: 'com.google.protobuf'

// 下記追加
protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.10.0"
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.4.0'
        }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
        }
    }
}
sourceSets {
    main {
        java {
            srcDirs 'build/generated/source/proto/main/grpc'
            srcDirs 'build/generated/source/proto/main/java'
        }
    }
}
dependencies {
    implementation('io.github.lognet:grpc-spring-boot-starter:3.4.1')
}

```

#### proto作成

```Company.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.hira.test.grpc";
option java_outer_classname = "CompanyProto";

package company;

// サービスメソッド定義
service Company {
    // 取得
    rpc Find (CompanyFindRequest) returns (CompanyFindReply);
    // 登録
    rpc Create (CompanyCreateRequest) returns (CompanyCreateReply);
    // 更新
    rpc Update (CompanyUpdateRequest) returns (CompanyUpdateReply);
    // 削除
    rpc Delete (CompanyDeleteRequest) returns (CompanyDeleteReply);
}

// 会社
message Company {
    // ID
    int32 id = 1;
    // 会社名
    string name = 2;
    // 住所
    string address = 3;
    // 電場番号
    string tel = 4;
}

// 付属情報
message Option {
     // 登録日時
     string createDt = 1;
     // 登録ユーザー
     string createUser = 2;
     // 更新日時
     string updateDt = 1;
     // 更新ユーザー
     string updateUser = 2;
}

// 取得リクエスト定義
message CompanyFindRequest{
    string id= 1;
}

// 取得レスポンス定義
message CompanyFindReply {
    Company company = 1;
    Option option = 2;
}

・・・（略）

```

Protoファイルをクライアントサイドとサーバサイドそれぞれでソース自動生成を行う。

GradleのgenerateProtoコマンドを実行するとbuild/generated/source/proto/main/配下に必要なソースが自動生成される。


#### サーバサイドの実装

```java:CompanyGrpcService.java

@GRpcService
public class CompanyGrpcService extends CompanyServiceGrpc.CompanyServiceImplBase {

　　// 会社リポジトリ
　　@Autowired
　　CompanyRepository companyRepository;

	/**
	 * 会社取得.
	 */
	@Override
	public void find(CompanyFindRequest request, StreamObserver<CompanyFindReply> responseObserver) {
		
		try {
			
	        // 会社取ID
			Company company = companyRepository.find(request.getId());
			
			CompanyFindReply.Builder builder = CompanyFindReply.newBuilder();
			
			if (company == null) {
				responseObserver.onError(new StatusException(Status.NOT_FOUND));
			} else {
				Company.Builder companyBuilder = builder.getCompanyBuilder();
				Option.Builder optionBuilder = builder.getOptionBuilder();
				companyBuilder.setId(company.getId());
				companyBuilder.setName(company.getName());

				builder.setPartnerCompany(partnerCompanyBuilder);
				builder.setOption(optionBuilder);

				responseObserver.onNext(builder.build());
				responseObserver.onCompleted();
			}
	        
		} catch(Exception e) {
			
			StatusRuntimeException exception = Status.INTERNAL
                    .withDescription(e.getMessage())
                    .withCause(e)
                    .asRuntimeException();
			
			responseObserver.onError(exception);
			
		}
}
```
#### クライアント（呼び出し）の実装

接続先のホストとポートを設定

 ```java:application.properties
app.grpc.test.url=xxx.xxx.xxx.xxx
app.grpc.test.port=xxxx
 ```

 ```java:AppConfig.java
@Component
@ConfigurationProperties(prefix = "app")
@Data
public class AppConfig {
	private Map<String, String> grpc;
}
 ```

ManagedChannelは、予め初期化しておいて呼び出す。


 ```java:GrpcConfig.java
@Component
@Data
public class GrpcConfig {
	
	@Autowired
	AppConfig config;
	
	private ManagedChannel channelTest;
	
	@PostConstruct
	void initGrpcManagedChannel() {
		channelTest = ManagedChannelBuilder
				.forAddress(
					config.getGrpc().get("test.url"), 
					Integer.parseInt(config.getGrpc().get("test.port")))
                .usePlaintext(true)
                .build();
	}
}
 ```
 ```java:CompanyMaintenanceService.java
 
@service
public class CompanyMaintenanceService {
   @Autowired
   GrpcConfig grpcConfig;

   public Company find(int id) {
       ManagedChannel channel = grpcConfig.getChannelTest();
       CompanyServiceGrpc.CompanyServiceBlockingStub stub = CompanyServiceGrpc.newBlockingStub(channel);
       // リクエスト作成
       CompanyFindRequest request = 
              CompanyFindRequest.newBuilder()
              .setId(id)
              .build();
       try {
　　　　　　// リクエスト実行
　　　　　　CompanyFindReply reply = stub.find(request);
　　　　　　// 結果取得
           Company company = reply.getCompany();
           Option option = reply.getOption();

　　　　　　・・・（略）

       } catch (StatusRuntimeException e) {
           if(Status.NOT_FOUND.getCode() == e.getStatus().getCode()) {
               // 404の処理
           }
       }
   }
}
 ```

### Protoのドキュメント化
protoc-gen-docを利用してProtoファイルからHTMLドキュメントを生成

```
protoc.exe --doc_out=html,index.html:doc proto/*.proto
```

#### 所感
リクエストとレスポンスのパラメータ情報の定義の簡単さや、protoファイルによる定義でソースやドキュメントを自動出力できるためクライアントとサーバの定義共有が容易。