5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Go]gRPCのAPIサーバー構築(ローカル) + コーディング規約の設定

5
Last updated at Posted at 2022-10-28

1. 概要

でgRPCの概要は学んだ。

が、実践で活かせるか?というとまだまだ不十分・・・・
ということで、より実践的な知見を得るため、

イチからgRPCのAPIサーバーをローカルで構築

をやってみた。
学習したい項目としては以下。

  • .protoのコーディング規約: gRPCでのAPI開発における一般的なやつ
  • .protoファイルからprotoc(Goプラグイン gRPCプラグイン)によるビルドで生成される(_grpc.pb.go, pb.go)ファイル(= gRPCサービスのインターフェース定義 & リクエストパラメータのシリアライズ・デシリアライズ処理)を使った 「gRPCサービスの実装」は、具体的にはどこをどうすればいいのか?
  • gRPCサーバーの起動に必要なmain関数への以下の記述ってどうすればいいのか? (どんな処理をどんな流れで書けばいいの?)
    • gRPCで使うプロトコル(TCPなど)・ポート番号の記述
    • gRPCサーバーへ処理対象とするgRPCサービスを登録
    • gRPCサーバーの起動
  • gRPCサーバーを起動するにはどうしたらいいのか?

2. 作成するAPIの想定と学習したい項目

APIの想定
以下のユースケースに沿うような「アカウント関連機能」のAPIを想定して作ってみる

  • アカウント作成
  • アカウント情報表示
  • アカウント情報更新
  • アカウント削除
  • 補足
    • アカウントは「ID」・「メールアドレス」・「パスワード」をフィールドとして持つメッセージとする。
    • DBとのつなぎ込みはせず、各リクエストに対して、レスポンスフォーマットに従った固定値のレスポンスを返す形にする

3. gRPCのAPIサーバーをローカルで構築する

3.1. APIの設計

「2. 作成するAPIの想定と学習したい項目」で挙げた「アカウント関連機能」のAPIをgRPCで実現するにあたり、
.protoに記述する内容を検討する。

3.1.1. .protoファイルのコーディング規約

.protoの記述の前に、以下のGoogle公式ドキュメントを参考に、コーディング規約を定めてみた。

注意:
Cloud APIs - API設計ガイド - リソース指向の設計や、Cloud Endpoints - 入門ガイド - APIの構成とデプロイ - HTTP/JSON の gRPC へのコード変換といったGoogle公式ドキュメントや、Qiita記事「Google の protobuf で gRPC Server に REST のエンドポイントをマッピング」を見るに、どうやら

  • REST APIの原則的なつくりは「HTTPメソッド(POST・GET・PUT・PATCH・DELETE)」 + 「操作対象のリソース名」という形であり、リールが単純。対してRPC APIにはそのような縛りがなく、設計で混乱しやすい
  • REST APIの方が広く普及している
  • REST APIで送信できるデータ量はRPC APIよりも少ない
    • 動画コンテンツなどではパフォーマンス上の理由からRESTは採用されることはほとんどなくRPC APIが使われている
    • 多くの企業がデータセンター内部ではソケットベースのRPC APIを使っている

といった事情を鑑みて、「RPC と REST 双方に対応したAPIを構築する」という方針に舵を切ることを前提として、
Cloud APIs - API設計ガイド - 標準メソッドは書かれているっぽい。

なお、RPCとREST双方に対応したAPIサーバシステムを構築する上では、
「RPC APIサーバー」の他に、以下の機能を持つ「リバースプロキシサーバー」を用意して、噛ませるというようなことをさせるらしい。
(参考: GitHub: grpc-ecosystem/grpc-gateway)

  • REST APIのリクエストをRPC APIのリクエストに変換
  • RPC APIのレスポンスをREST APIのレスポンスに変換

リバースプロキシサーバーのプログラムを用意するには、以下が必要になるらしい。

  • Protol Bufferコンパイラのプラグイン「gRPC-Gateway」をインストール
  • .protoファイル内のサービス定義でgoogle.api.httpアノテーションを使用した「gRPC API」と「REST API」の紐付けを行う(「HTTPメソッド」「エンドポイントURL」「エンドポイントURLのパスパラメータ」について紐付けを行う)

この記事では、そこまですることは想定しておらず RPC API一本でいく ことを前提とする。

なので、「1.1.2. サービスのRPCメソッドの型定義、採用するHTTPメソッド、リクエストパラメータの受け取り方」には書いてしまったが、RPCメソッドの

  • HTTPメソッド
  • リクエストパラメータの受け取り方
    については、.protoファイルの記述する上では「無視」し、google.api.httpアノテーションによる紐付けもしないことにした。

3.1.1.1 コーディング規約 - ファイルフォーマット

参考:

下記、考慮すべき項目。


  • 1行は80文字以内
  • インデントは半角スペース2つ
  • 文字列はダブルクォートで囲む
  • ファイル内の構成は以下の順番にする
    • コメント: 「著作権」と「ライセンス」
    • syntax文
    • package文
    • import文
    • option文
    • コメント: APIの概要
    • サービスの定義
    • メッセージの定義(リクエストとレスポンス)
      • RPCメソッド1のリクエスト
      • RPCメソッド1のレスポンス
      • RPCメソッド2のリクエスト
      • RPCメソッド2のレスポンス
        ...
    • メッセージの定義(リソース)
      • 親リソース
      • 子リソース
        ...
  • 「サービス名」、「RPCメソッド名」、「メッセージタイプ名」: アッパーキャメルケース = パスカルケース(XxxXxx)
  • 「メッセージのフィールド名」: スネークケース(xxx_xxx)
  • ファイル名: スネークケース

3.1.1.2. コーディング規約 - サービスのRPCメソッドの型定義、採用するHTTPメソッド、リクエストパラメータの受け取り方

「3.1.1. .protoのコーディング規約」にも書いたが、

  • HTTPメソッド
  • リクエストパラメータの受け取り方
    は以下に書いてしまったが、REST APIも同時実装する場合の項目なので今回、.protoファイルを記述する時点で無視するものとする。

参考:

下記、考慮すべき項目。


  • サービスの型定義に関する命名規則・採用するHTTPメソッド・リクエストパラメータの受け取り方
    • サービス名: <リソース名>Service
    • RPCメソッド:
      • 生成:
        • メソッド名: Create<リソース名>
        • 引数の型: Create<リソース名>Request
        • 返り値の型: <リソース名>
        • HTTPメソッド: POST
        • リクエストパラメータの受け取り方:
          • URLパス: 紐付ける親リソースの指定(<親リソース名>s/<親リソースの識別子の値>)
          • リクエストボディ: 生成する自身のリソース(ID、その他フィールド)
          • URLクエリパラメータ: その他の情報
      • 表示:
        • メソッド名: Get<リソース名>
        • 引数の型: Get<リソース名>Request
        • 返り値の型: <リソース名>
        • HTTPメソッド: GET
        • リクエストパラメータの受け取り方:
          • URLパス: 表示対象の「自身のリソースを特定するための親リソースを含めたパス」(<親リソース名>s/<親リソースの識別子の値>/<子リソース名>s/<子リソースの識別子の値>)
          • リクエストボディ: なし
          • URLクエリパラメータ: その他の情報
      • 一覧表示:
        • メソッド名: List<リソース名>s
        • 引数の型: List<リソース名>sRequest
        • 返り値の型: List<リソース名>sResponse
        • HTTPメソッド: GET
        • リクエストパラメータの受け取り方:
          • URLパス: 表示対象の「自身のリソース群が紐づく、親のリソースを特定するためのパス」(<親リソース名>/<親リソースの識別子の値>)
          • リクエストボディ: なし
          • URLクエリパラメータ: その他の情報(自身のリソースの情報をいくつ取得するか?など)
      • 更新:
        • メソッド名: Update<リソース名>
        • 引数の型: Update<リソース名>Request
        • 返り値の型: <リソース名>
        • HTTPメソッド: PATCH ※完全な更新(が何を指すかはわからないが)のみとする場合はPUT。そもそも完全な更新は非推奨とのこと
        • リクエストパラメータの受け取り方:
          • URLパス: 更新対象の「自身のリソースを特定するための親リソースを含めたパス」(<親リソース名>s/<親リソースの識別子の値>/<子リソース名>s/<子リソースの識別子の値>)
          • リクエストボディ: (更新後の)リソース情報
          • URLクエリパラメータ: その他の情報
      • 削除:
        • メソッド名: Delete<リソース名>
        • 引数の型: Delete<リソース名>Request
        • 返り値の型: google.protobuf.Empty
        • HTTPメソッド: DELETE
        • リクエストパラメータの受け取り方:
          • URLパス: 削除対象の「自身のリソースを特定するための親リソースを含めたパス」(<親リソース名>s/<親リソースの識別子の値>/<子リソース名>s/<子リソースの識別子の値>)
          • リクエストボディ: なし
          • URLクエリパラメータ: その他の情報

3.1.1.3. メッセージの型定義における命名規則とフィールドの内容

参考:

「リソース自体」「リクエストパラメータ」「レスポンスパラメータ」のメッセージの型を定義するにあたって下記、考慮すべき項目。


  • メッセージの型
    • リソース自体の型
      • メッセージ名: <リソース名>
      • フィールド:
        • 識別子
        • 識別子以外のリソース自体のフィールド
    • 生成
      • リクエストメッセージの型
        • メッセージ名: Create<リソース名>Request
        • フィールド:
          • 親のリソースを特定するためのパス(<親リソース名>/<親リソースの識別子の値>)
          • 自身のリソースの識別子
          • 自身のリソース ※リソース内のフィールドのウチ、識別子は↑で指定されたものを使う(?)
      • レスポンスメッセージの型: なし(生成したリソース自体を返し、リソース自体のメッセージの型は別途定義しているためここでは定義の必要なし)
    • 表示
      • リクエストメッセージの型
        • メッセージ名: Get<リソース名>Request
        • フィールド:
          • 自身のリソースを特定するための親リソースを含めたパス(<親リソース名>s/<親リソースの識別子の値>/<子リソース名>s/<子リソースの識別子の値>)
      • レスポンスメッセージの型: なし(対象のリソース自体を返し、リソース自体のメッセージの型は別途定義しているためここでは定義の必要なし)
    • 一覧表示
      • リクエストメッセージの型
        • メッセージ名: List<リソース名>sRequest
        • フィールド:
          • 表示対象の自身のリソース群が紐づく、親のリソースを特定するためのパス(<親リソース名>/<親リソースの識別子の値>)
          • 返してもらう自身のリソースの個数 など
      • レスポンスメッセージの型
        • メッセージ名: List<リソース名>sResponse
        • フィールド
          • リソースの配列
    • 更新
      • リクエストメッセージの型
        • メッセージ名: Update<リソース名>Request
        • フィールド:
          • 自身のリソース
      • レスポンスメッセージの型: なし(更新済みの対象のリソース自体を返し、リソース自体のメッセージの型は別途定義しているためここでは定義の必要なし)
    • 削除
      • リクエストメッセージの型
        • メッセージ名: Delete<リソース名>Request
        • フィールド: 自身のリソースを特定するための親リソースを含めたパス(<親リソース名>s/<親リソースの識別子の値>/<子リソース名>s/<子リソースの識別子の値>)
      • レスポンスメッセージの型: なし (google.protobuf.Empty を返すため、ここでは定義の必要なし)

3.1.2. 外部設計

3.1.1. で設定したコーディング規約を踏まえて、.protoファイルに記述する内容を検討する。

※先述の通り、REST APIの同時実装はこの記事では考えていないため、
「サービスのインターフェース定義」についてgoogle.api.httpアノテーションに絡む「HTTPメソッド」、「リクエストパラメータの受け取り方(URLパスパラメータ/URLクエリパラメータ/リクエストボディ)」については検討しない。
また「メッセージの定義」では、「操作対象リソースの特定のためのフィールド」は「URLパスパラメータ」を持たせるみたいなコーディング規約としていたが、こちらもgRPC API一本でいく場合は必要なく、むしろ意味がないというかそうする理由がわからん・・・となってしまうため、「リソースの識別子」を持たせることにした。


メモ: サービスの定義を検討するにあたっては、
リクエストパラメータ、レスポンスパラメータの型を先に決定しておかないとやりづらいな・・・・とわかったので、
「メッセージの定義 → サービスの定義の順に検討」する。

3.1.2.1. 外部設計 - メッセージの定義

(対象リソース自体のデータ構造、リクエストパラメータ、レスポンスパラメータのサーバー上でのデータ構造)

●アカウントリソース自体

  • メッセージ名: Account
  • メッセージのフィールド:
    • 識別子: id(string)
    • メールアドレス: email(string)
    • パスワード: password(string)

●アカウント作成API

  • リクエストパラメータ
    • メッセージタイプ名: CreateAccountRequest
    • メッセージのフィールド:
      • アカウントリソース: account(Account)
  • レスポンスパラメータ
    • なし(作成した「Account」リソース自体を返すが、「Accountリソース自体」のメッセージは別途定義しているため)

●アカウント情報表示API

  • リクエストパラメータ
    • メッセージタイプ名: GetAccountRequest
    • メッセージのフィールド:
      • 識別子: id(string)
  • レスポンスパラメータ
    • なし(表示対象の「Account」リソース自体を返すが、「Accountリソース自体」のメッセージは別途定義しているため)

●アカウント情報更新API

  • リクエストパラメータ
    • メッセージタイプ名: UpdateAccountRequest
    • メッセージのフィールド:
      • アカウントリソース: account(Account)
  • レスポンスパラメータ
    •  なし (更新後の「Account」リソース自体を返すが、「Accountリソース自体」のメッセージは別途定義しているため)

●アカウント削除API

  • リクエストパラメータ
    • メッセージタイプ名: DeleteAccountRequest
    • メッセージのフィールド:
      • 識別子: id(string)
  • レスポンスパラメータ
    • なし (google.protobuf.Empty を返すため)

3.1.2.2. 外部設計 - サービスの定義

  • サービス名 : AccountService
  • サービス「AccountService」のRPCメソッド
    • 生成:
      • メソッド名: CreateAccount
      • 引数の型: CreateAccountRequest
      • 返り値の型: Account
    • 表示:
      • メソッド名: GetAccount
      • 引数の型: GetAccountRequest
      • 返り値の型: Account
    • 更新:
      • メソッド名: UpdateAccount
      • 引数の型: UpdateAccountRequest
      • 返り値の型: Account
    • 削除:
      • メソッド名: DeleteAccount
      • 引数の型: DeleteAccountRequest
      • 返り値の型: google.protobuf.Empty

3.2. APIの実装

3.1.2. の外部設計を踏まえて、以下の工程を実施する。

  1. .protoファイルの作成
  2. .protoファイルから「gRPCサービスインターフェース定義ファイル(_grpc.pb.go)」と「メッセージ定義ファイル(.pg.go)」を生成
  3. gRPCサービスの実装を記述

3.2.0. 前準備(プロジェクト構成の決定)

.protoファイルの作成しようとしたが、そもそもどこに作ればいいんや?となり、
先にプロジェクト構成を決めておかねばならない ことがわかった。

  • 普通はモジュール化する よね
  • 「A. サービス別でディレクトリ切って.protoとビルド成果物の.pb.goを配置する」 vs 「B. .protoだけのディレクトリを作る + ビルド成果物の .pb.goだけのディレクトリを作る」 どっちがいいか?
    →ビルド時にビルド対象の.protoをまとめて指定しやすいB. の方がよさそう
  • サービスの実装コードはサンプルだとmain.goに書かれてたけど、「APIのサービスが増えてったらmain.go内でインターフェースに対する構造体定義とそのメソッド定義が無限に爆発するよね」、ということで 「main.go と サービスの実装はファイルをわける」 のがよさそう
  • ファイル名はサービスに関わるものはその操作対象のリソース名つけとくのが無難そう

・・・・といったことなどを踏まえ、今回はモジュール「sample_grpc_apis」としてGitHubで公開する ことも想定し、以下の最終形を想定することにした。

プロジェクト構成

sample_grpc_apis/
  cmd/
    api/
      main.go          # メイン処理(指定プロトコル・ポートでgRPCサーバーを起動)を記述
  pb/                  # .protoファイルからビルドしてできた成果物を格納するディレクトリ
    account.pb.go      # 「アカウント関連機能」APIのリクエストパラメータ・レスポンスパラメータのシリアライズ・デシリアライズ処理のコード
    account_grpc.pb.go # 「アカウント関連機能」APIのgRPCサービスのインターフェースコード
  proto/               # .protoファイル格納ディレクトリ
    accout.proto       # 「アカウント関連機能」APIの.protoファイル
  service/             # gRPCサービスの実装コードを格納するディレクトリ
    account_service.go # 「アカウント関連機能」APIのgRPCサービスの実装コード
  go.mod               # sample_grpc_apisモジュールの置き場、対象Goバージョン、依存先モジュールの定義
  go.sum               
  README.txt           # readme

モジュールの作成は[Go]パッケージ・モジュールまとめ - 3. モジュールの作成手順を参考に実施する。

3.2.1. .protoファイルの作成

以下の内容で.protoファイルを作成する

proto/account.proto
// Protocol Buffersのバージョン指定
syntax = "proto3";

// パッケージ名(あくまでProtocol Buffersの世界でのパッケージ名であり、Goのパッケージとは無関係)
package xxxx;

// 他の.protoファイルの読み込み(ほかファイルで定義した型を使いたいときとか)
// protocコマンド実行時に指定する「--proto_path」オプションで指定するパスを基準として、相対パスで記述。
import "google/protobuf/empty.proto"; // google.protobuf.Emptyを使いたいので

// .protoファイルからビルドして生成される「メッセージ定義のソースコード」、「gRPCサービスインターフェース定義のソースコード」の出力先ディレクトリ
// protocコマンド実行時の「--go_out」オプション、「--go-grpc_out」オプションと関連する
option go_package = "pb/";

// --- サービス定義
service AccountService {
  // アカウント作成
  rpc CreateAccount (CreateAccountRequest) returns (Account) {}

  // アカウント情報表示
  rpc GetAccount (GetAccountRequest) returns (Account) {}

  // アカウント情報更新
  rpc UpdateAccount (UpdateAccountRequest) returns (Account) {}

  // アカウント削除
  rpc DeleteAccount (DeleteAccountRequest) returns (google.protobuf.Empty) {}
}


// --- メッセージ定義
// --- リクエスト & レスポンス
// [アカウント作成]リクエストメッセージ
message CreateAccountRequest {
  Account account = 1;   // 作成する「アカウント」リソース自体(ただしidは設定不要)
}

// [アカウント情報取得]リクエストメッセージ
message GetAccountRequest {
  string id = 1; // 識別子
}

// [アカウント情報更新]リクエストメッセージ
message UpdateAccountRequest {
  Account account = 1; // 更新する「アカウント」リソース自体(渡された「Account」内「id」にマッチするデータストア上の「Account」を更新)
}

// [アカウント削除]リクエストメッセージ
message DeleteAccountRequest {
  // 渡された「id」にマッチするデータストア上の「Account」を削除
  string id = 1; // 識別子
}

// リソース自体
// [アカウントリソース]
message Account {
  string id = 1;       // 識別子
  string email = 2;    // メールアドレス
  string password = 3; // パスワード
}


3.2.2. .protoファイルから「RPCサービスインターフェース定義ファイル(_grpc.pb.go)」と「メッセージ定義ファイル(.pg.go)」を生成

前提:
protocコマンドの解説

protoc --go_out=<.pb.goファイル(=シリアライズ・デシリアライズ処理のコードの出力ディレクトリパス> \
       --go-grpc_out=<_grpc.pb.goファイル(=RPCメソッドのインターフェースのコード)の出力ディレクトリパス> \
       --proto_path=<.protoファイル内で別の.protoファイルをimportする時のベースパス> \
       <コンパイル対象の.protoファイルのパス>

メモ:

  • .pg.goファイルの出力先: <--go_outで指定したパス> + <.protoファイル内の「go_package」で指定したパス>
  • _grpc.pg.goファイルの出力先: <--go-grpc_outで指定したパス> + <.protoファイル内の「go_package」で指定したパス>

以下のコマンドを実行して、account.protoから「RPCサービスインターフェース定義ファイル(_grpc.pb.go)」と「メッセージ定義ファイル(.pb.go)」を生成する。

# Goプロジェクトルートディレクトリへ移動
cd <プロジェクトルート>

# protocコマンドで``account.proto``ファイルからビルド
protoc --go_out=. --go-grpc_out=. proto/account.proto
=>pb/account.pb.go, pb/account_grpc.pb.go が出力される。

それぞれの中身は以下の通り

pb/account.pb.go
// Protocol Buffersのバージョン指定

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// 	protoc-gen-go v1.28.1
// 	protoc        v3.19.3
// source: proto/account.proto

package pb

import (
	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
	emptypb "google.golang.org/protobuf/types/known/emptypb"
	reflect "reflect"
	sync "sync"
)

const (
	// Verify that this generated code is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
	// Verify that runtime/protoimpl is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

// --- メッセージ定義
// --- リクエスト & レスポンス
// [アカウント作成]リクエストメッセージ
type CreateAccountRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` // 作成する「アカウント」リソース自体(ただしidは設定不要)
}

func (x *CreateAccountRequest) Reset() {
	*x = CreateAccountRequest{}
	if protoimpl.UnsafeEnabled {
		mi := &file_proto_account_proto_msgTypes[0]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *CreateAccountRequest) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*CreateAccountRequest) ProtoMessage() {}

func (x *CreateAccountRequest) ProtoReflect() protoreflect.Message {
	mi := &file_proto_account_proto_msgTypes[0]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use CreateAccountRequest.ProtoReflect.Descriptor instead.
func (*CreateAccountRequest) Descriptor() ([]byte, []int) {
	return file_proto_account_proto_rawDescGZIP(), []int{0}
}

func (x *CreateAccountRequest) GetAccount() *Account {
	if x != nil {
		return x.Account
	}
	return nil
}

// [アカウント情報取得]リクエストメッセージ
type GetAccountRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // 識別子
}

func (x *GetAccountRequest) Reset() {
	*x = GetAccountRequest{}
	if protoimpl.UnsafeEnabled {
		mi := &file_proto_account_proto_msgTypes[1]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *GetAccountRequest) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*GetAccountRequest) ProtoMessage() {}

func (x *GetAccountRequest) ProtoReflect() protoreflect.Message {
	mi := &file_proto_account_proto_msgTypes[1]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use GetAccountRequest.ProtoReflect.Descriptor instead.
func (*GetAccountRequest) Descriptor() ([]byte, []int) {
	return file_proto_account_proto_rawDescGZIP(), []int{1}
}

func (x *GetAccountRequest) GetId() string {
	if x != nil {
		return x.Id
	}
	return ""
}

// [アカウント情報更新]リクエストメッセージ
type UpdateAccountRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` // 更新する「アカウント」リソース自体(渡された「Account」内「id」にマッチするデータストア上の「Account」を更新)
}

func (x *UpdateAccountRequest) Reset() {
	*x = UpdateAccountRequest{}
	if protoimpl.UnsafeEnabled {
		mi := &file_proto_account_proto_msgTypes[2]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *UpdateAccountRequest) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*UpdateAccountRequest) ProtoMessage() {}

func (x *UpdateAccountRequest) ProtoReflect() protoreflect.Message {
	mi := &file_proto_account_proto_msgTypes[2]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use UpdateAccountRequest.ProtoReflect.Descriptor instead.
func (*UpdateAccountRequest) Descriptor() ([]byte, []int) {
	return file_proto_account_proto_rawDescGZIP(), []int{2}
}

func (x *UpdateAccountRequest) GetAccount() *Account {
	if x != nil {
		return x.Account
	}
	return nil
}

// [アカウント削除]リクエストメッセージ
type DeleteAccountRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	// 渡された「id」にマッチするデータストア上の「Account」を削除
	Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // 識別子
}

func (x *DeleteAccountRequest) Reset() {
	*x = DeleteAccountRequest{}
	if protoimpl.UnsafeEnabled {
		mi := &file_proto_account_proto_msgTypes[3]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *DeleteAccountRequest) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*DeleteAccountRequest) ProtoMessage() {}

func (x *DeleteAccountRequest) ProtoReflect() protoreflect.Message {
	mi := &file_proto_account_proto_msgTypes[3]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use DeleteAccountRequest.ProtoReflect.Descriptor instead.
func (*DeleteAccountRequest) Descriptor() ([]byte, []int) {
	return file_proto_account_proto_rawDescGZIP(), []int{3}
}

func (x *DeleteAccountRequest) GetId() string {
	if x != nil {
		return x.Id
	}
	return ""
}

// リソース自体
// [アカウントリソース]
type Account struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Id       string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`             // 識別子
	Email    string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"`       // メールアドレス
	Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` // パスワード
}

func (x *Account) Reset() {
	*x = Account{}
	if protoimpl.UnsafeEnabled {
		mi := &file_proto_account_proto_msgTypes[4]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *Account) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*Account) ProtoMessage() {}

func (x *Account) ProtoReflect() protoreflect.Message {
	mi := &file_proto_account_proto_msgTypes[4]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use Account.ProtoReflect.Descriptor instead.
func (*Account) Descriptor() ([]byte, []int) {
	return file_proto_account_proto_rawDescGZIP(), []int{4}
}

func (x *Account) GetId() string {
	if x != nil {
		return x.Id
	}
	return ""
}

func (x *Account) GetEmail() string {
	if x != nil {
		return x.Email
	}
	return ""
}

func (x *Account) GetPassword() string {
	if x != nil {
		return x.Password
	}
	return ""
}

var File_proto_account_proto protoreflect.FileDescriptor

var file_proto_account_proto_rawDesc = []byte{
	0x0a, 0x13, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e,
	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x78, 0x78, 0x78, 0x78, 0x1a, 0x1b, 0x67, 0x6f, 0x6f,
	0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70,
	0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x3f, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61,
	0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
	0x12, 0x27, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
	0x0b, 0x32, 0x0d, 0x2e, 0x78, 0x78, 0x78, 0x78, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
	0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x23, 0x0a, 0x11, 0x47, 0x65, 0x74,
	0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e,
	0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x3f,
	0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52,
	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
	0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x78, 0x78, 0x78, 0x78, 0x2e, 0x41,
	0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22,
	0x26, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
	0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4b, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75,
	0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
	0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28,
	0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73,
	0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73,
	0x77, 0x6f, 0x72, 0x64, 0x32, 0x8b, 0x02, 0x0a, 0x0e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
	0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74,
	0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x2e, 0x78, 0x78, 0x78, 0x78, 0x2e,
	0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71,
	0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x78, 0x78, 0x78, 0x78, 0x2e, 0x41, 0x63, 0x63, 0x6f,
	0x75, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f,
	0x75, 0x6e, 0x74, 0x12, 0x17, 0x2e, 0x78, 0x78, 0x78, 0x78, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63,
	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x78,
	0x78, 0x78, 0x78, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x3c, 0x0a,
	0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a,
	0x2e, 0x78, 0x78, 0x78, 0x78, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f,
	0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x78, 0x78, 0x78,
	0x78, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0d, 0x44,
	0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x2e, 0x78,
	0x78, 0x78, 0x78, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
	0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
	0x22, 0x00, 0x42, 0x05, 0x5a, 0x03, 0x70, 0x62, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
	0x33,
}

var (
	file_proto_account_proto_rawDescOnce sync.Once
	file_proto_account_proto_rawDescData = file_proto_account_proto_rawDesc
)

func file_proto_account_proto_rawDescGZIP() []byte {
	file_proto_account_proto_rawDescOnce.Do(func() {
		file_proto_account_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_account_proto_rawDescData)
	})
	return file_proto_account_proto_rawDescData
}

var file_proto_account_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_proto_account_proto_goTypes = []interface{}{
	(*CreateAccountRequest)(nil), // 0: xxxx.CreateAccountRequest
	(*GetAccountRequest)(nil),    // 1: xxxx.GetAccountRequest
	(*UpdateAccountRequest)(nil), // 2: xxxx.UpdateAccountRequest
	(*DeleteAccountRequest)(nil), // 3: xxxx.DeleteAccountRequest
	(*Account)(nil),              // 4: xxxx.Account
	(*emptypb.Empty)(nil),        // 5: google.protobuf.Empty
}
var file_proto_account_proto_depIdxs = []int32{
	4, // 0: xxxx.CreateAccountRequest.account:type_name -> xxxx.Account
	4, // 1: xxxx.UpdateAccountRequest.account:type_name -> xxxx.Account
	0, // 2: xxxx.AccountService.CreateAccount:input_type -> xxxx.CreateAccountRequest
	1, // 3: xxxx.AccountService.GetAccount:input_type -> xxxx.GetAccountRequest
	2, // 4: xxxx.AccountService.UpdateAccount:input_type -> xxxx.UpdateAccountRequest
	3, // 5: xxxx.AccountService.DeleteAccount:input_type -> xxxx.DeleteAccountRequest
	4, // 6: xxxx.AccountService.CreateAccount:output_type -> xxxx.Account
	4, // 7: xxxx.AccountService.GetAccount:output_type -> xxxx.Account
	4, // 8: xxxx.AccountService.UpdateAccount:output_type -> xxxx.Account
	5, // 9: xxxx.AccountService.DeleteAccount:output_type -> google.protobuf.Empty
	6, // [6:10] is the sub-list for method output_type
	2, // [2:6] is the sub-list for method input_type
	2, // [2:2] is the sub-list for extension type_name
	2, // [2:2] is the sub-list for extension extendee
	0, // [0:2] is the sub-list for field type_name
}

func init() { file_proto_account_proto_init() }
func file_proto_account_proto_init() {
	if File_proto_account_proto != nil {
		return
	}
	if !protoimpl.UnsafeEnabled {
		file_proto_account_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*CreateAccountRequest); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_proto_account_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*GetAccountRequest); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_proto_account_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*UpdateAccountRequest); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_proto_account_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*DeleteAccountRequest); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_proto_account_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*Account); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
	}
	type x struct{}
	out := protoimpl.TypeBuilder{
		File: protoimpl.DescBuilder{
			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
			RawDescriptor: file_proto_account_proto_rawDesc,
			NumEnums:      0,
			NumMessages:   5,
			NumExtensions: 0,
			NumServices:   1,
		},
		GoTypes:           file_proto_account_proto_goTypes,
		DependencyIndexes: file_proto_account_proto_depIdxs,
		MessageInfos:      file_proto_account_proto_msgTypes,
	}.Build()
	File_proto_account_proto = out.File
	file_proto_account_proto_rawDesc = nil
	file_proto_account_proto_goTypes = nil
	file_proto_account_proto_depIdxs = nil
}

pb/account_grpc.pb.go
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc             v3.19.3
// source: proto/account.proto

package pb

import (
	context "context"
	grpc "google.golang.org/grpc"
	codes "google.golang.org/grpc/codes"
	status "google.golang.org/grpc/status"
	emptypb "google.golang.org/protobuf/types/known/emptypb"
)

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7

// AccountServiceClient is the client API for AccountService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type AccountServiceClient interface {
	// アカウント作成
	CreateAccount(ctx context.Context, in *CreateAccountRequest, opts ...grpc.CallOption) (*Account, error)
	// アカウント情報表示
	GetAccount(ctx context.Context, in *GetAccountRequest, opts ...grpc.CallOption) (*Account, error)
	// アカウント情報更新
	UpdateAccount(ctx context.Context, in *UpdateAccountRequest, opts ...grpc.CallOption) (*Account, error)
	// アカウント削除
	DeleteAccount(ctx context.Context, in *DeleteAccountRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
}

type accountServiceClient struct {
	cc grpc.ClientConnInterface
}

func NewAccountServiceClient(cc grpc.ClientConnInterface) AccountServiceClient {
	return &accountServiceClient{cc}
}

func (c *accountServiceClient) CreateAccount(ctx context.Context, in *CreateAccountRequest, opts ...grpc.CallOption) (*Account, error) {
	out := new(Account)
	err := c.cc.Invoke(ctx, "/xxxx.AccountService/CreateAccount", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (c *accountServiceClient) GetAccount(ctx context.Context, in *GetAccountRequest, opts ...grpc.CallOption) (*Account, error) {
	out := new(Account)
	err := c.cc.Invoke(ctx, "/xxxx.AccountService/GetAccount", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (c *accountServiceClient) UpdateAccount(ctx context.Context, in *UpdateAccountRequest, opts ...grpc.CallOption) (*Account, error) {
	out := new(Account)
	err := c.cc.Invoke(ctx, "/xxxx.AccountService/UpdateAccount", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (c *accountServiceClient) DeleteAccount(ctx context.Context, in *DeleteAccountRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	out := new(emptypb.Empty)
	err := c.cc.Invoke(ctx, "/xxxx.AccountService/DeleteAccount", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

// AccountServiceServer is the server API for AccountService service.
// All implementations must embed UnimplementedAccountServiceServer
// for forward compatibility
type AccountServiceServer interface {
	// アカウント作成
	CreateAccount(context.Context, *CreateAccountRequest) (*Account, error)
	// アカウント情報表示
	GetAccount(context.Context, *GetAccountRequest) (*Account, error)
	// アカウント情報更新
	UpdateAccount(context.Context, *UpdateAccountRequest) (*Account, error)
	// アカウント削除
	DeleteAccount(context.Context, *DeleteAccountRequest) (*emptypb.Empty, error)
	mustEmbedUnimplementedAccountServiceServer()
}

// UnimplementedAccountServiceServer must be embedded to have forward compatible implementations.
type UnimplementedAccountServiceServer struct {
}

func (UnimplementedAccountServiceServer) CreateAccount(context.Context, *CreateAccountRequest) (*Account, error) {
	return nil, status.Errorf(codes.Unimplemented, "method CreateAccount not implemented")
}
func (UnimplementedAccountServiceServer) GetAccount(context.Context, *GetAccountRequest) (*Account, error) {
	return nil, status.Errorf(codes.Unimplemented, "method GetAccount not implemented")
}
func (UnimplementedAccountServiceServer) UpdateAccount(context.Context, *UpdateAccountRequest) (*Account, error) {
	return nil, status.Errorf(codes.Unimplemented, "method UpdateAccount not implemented")
}
func (UnimplementedAccountServiceServer) DeleteAccount(context.Context, *DeleteAccountRequest) (*emptypb.Empty, error) {
	return nil, status.Errorf(codes.Unimplemented, "method DeleteAccount not implemented")
}
func (UnimplementedAccountServiceServer) mustEmbedUnimplementedAccountServiceServer() {}

// UnsafeAccountServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AccountServiceServer will
// result in compilation errors.
type UnsafeAccountServiceServer interface {
	mustEmbedUnimplementedAccountServiceServer()
}

func RegisterAccountServiceServer(s grpc.ServiceRegistrar, srv AccountServiceServer) {
	s.RegisterService(&AccountService_ServiceDesc, srv)
}

func _AccountService_CreateAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(CreateAccountRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(AccountServiceServer).CreateAccount(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/xxxx.AccountService/CreateAccount",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(AccountServiceServer).CreateAccount(ctx, req.(*CreateAccountRequest))
	}
	return interceptor(ctx, in, info, handler)
}

func _AccountService_GetAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(GetAccountRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(AccountServiceServer).GetAccount(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/xxxx.AccountService/GetAccount",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(AccountServiceServer).GetAccount(ctx, req.(*GetAccountRequest))
	}
	return interceptor(ctx, in, info, handler)
}

func _AccountService_UpdateAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(UpdateAccountRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(AccountServiceServer).UpdateAccount(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/xxxx.AccountService/UpdateAccount",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(AccountServiceServer).UpdateAccount(ctx, req.(*UpdateAccountRequest))
	}
	return interceptor(ctx, in, info, handler)
}

func _AccountService_DeleteAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(DeleteAccountRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(AccountServiceServer).DeleteAccount(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/xxxx.AccountService/DeleteAccount",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(AccountServiceServer).DeleteAccount(ctx, req.(*DeleteAccountRequest))
	}
	return interceptor(ctx, in, info, handler)
}

// AccountService_ServiceDesc is the grpc.ServiceDesc for AccountService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var AccountService_ServiceDesc = grpc.ServiceDesc{
	ServiceName: "xxxx.AccountService",
	HandlerType: (*AccountServiceServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "CreateAccount",
			Handler:    _AccountService_CreateAccount_Handler,
		},
		{
			MethodName: "GetAccount",
			Handler:    _AccountService_GetAccount_Handler,
		},
		{
			MethodName: "UpdateAccount",
			Handler:    _AccountService_UpdateAccount_Handler,
		},
		{
			MethodName: "DeleteAccount",
			Handler:    _AccountService_DeleteAccount_Handler,
		},
	},
	Streams:  []grpc.StreamDesc{},
	Metadata: "proto/account.proto",
}

これを観察すると、以下の事実がわかる。

  • .protoファイル内に記述したコメントもそのまま.pb.go, _grpc.pb.goファイル内に出力される
  • サービス定義の変換
    • service XxxService から以下が生成されている
      • type XxxServiceYyy interface (YyyはServer, Clientの二種類が同時生成されている)
      • type xxxServiceYyy struct (YyyはServer, Clientの二種類が同時生成されている)
      • ``func NewXxxServiceYyy(cc grpc.ClientConnInterface) XxxServiceYyy
      • type UnimplementedXxxServiceServer struct
        • UnimplementedAccountServiceServer は、前方互換性のある実装を持つために埋め込む必要があります。

3.2.3. gRPCサービスの実装を記述

pb/account_grpc.pb.goに書かれているサービス「AccountServiceServer」のインターフェースについて、service/account_service.goファイルを作成し、実装していく

インターフェース

pb/account_grpc.pb.go
...

// AccountServiceServer is the server API for AccountService service.
// All implementations must embed UnimplementedAccountServiceServer
// for forward compatibility
type AccountServiceServer interface {
	// アカウント作成
	CreateAccount(context.Context, *CreateAccountRequest) (*Account, error)
	// アカウント情報表示
	GetAccount(context.Context, *GetAccountRequest) (*Account, error)
	// アカウント情報更新
	UpdateAccount(context.Context, *UpdateAccountRequest) (*Account, error)
	// アカウント削除
	DeleteAccount(context.Context, *DeleteAccountRequest) (*emptypb.Empty, error)
	mustEmbedUnimplementedAccountServiceServer()
}

実装

service/account_service.go
// アカウント関連サービスの実装

package service // serviceディレクトリにあるので・・・

import (
  "log" // 何かと使うので。
  "github.com/<オーナー名>/sample_grpc_apis/pb" // このパッケージの「AccountServiceServer」インターフェースの実装をするので。
  "google.golang.org/protobuf/types/known/emptypb"  // emptypb.Empty使うので
  "context" // context.Context使うので
)

// 以下、本質的な部分になる「account_grpc.pb.go」の「AccountServiceServer」インターフェースの実装

// -- 構造体
type AccountServiceServer struct { 
	pb.UnimplementedAccountServiceServer
}

// -- 構造体をレシーバとするメソッド
// アカウント作成
// [レシーバ] AccountServiceServerのポインタ
// [引数] 
//  ctx: ??? (Context)
//  req: アカウント作成リクエストメッセージ (CreateAccountRequestのポインタ)
// [返り値]
//  アカウントリソース (Accountのポインタ)
//  エラー (error)
func (s *AccountServiceServer) CreateAccount(ctx context.Context, req *pb.CreateAccountRequest) (*pb.Account, error) {
  log.Printf("[start]CreateAccount req: %v\n", req)

  // IDを生成
  account_id := "1"

  log.Println("[end]CreateAccount")
  return &pb.Account{ Id: account_id, Email: req.Account.Email, Password: req.Account.Password }, nil
}

// アカウント情報表示
// [レシーバ] AccountServiceServerのポインタ
// [引数] 
//  ctx: ??? (Context)
//  req: アカウント作成リクエストメッセージ (GetAccountRequestのポインタ)
// [返り値]
//  アカウントリソース (Accountのポインタ)
//  エラー (error)
func (s *AccountServiceServer) GetAccount(ctx context.Context, req *pb.GetAccountRequest) (*pb.Account, error) {
  log.Printf("[start]GetAccount req: %v\n", req)

  log.Println("[end]GetAccount")
  return &pb.Account{ Id: req.Id, Email: "test@example.com", Password: "test" }, nil
}

// アカウント情報更新
// [レシーバ] AccountServiceServerのポインタ
// [引数] 
//  ctx: ??? (Context)
//  req: アカウント作成リクエストメッセージ (UpdateAccountRequestのポインタ)
// [返り値]
//  アカウントリソース (Accountのポインタ)
//  エラー (error)
func (s *AccountServiceServer) UpdateAccount(ctx context.Context, req *pb.UpdateAccountRequest) (*pb.Account, error) {
  log.Printf("[start]UpdateAccount req: %v\n", req)

  // アカウント情報更新処理をここへ書く

  log.Println("[end]UpdateAccount")
  return &pb.Account{ Id: req.Account.Id, Email: req.Account.Email, Password: req.Account.Password }, nil
}

// アカウント削除
// [レシーバ] DeleteServiceServerのポインタ
// [引数] 
//  ctx: ??? (Context)
//  req: アカウント作成リクエストメッセージ (DeleteAccountRequestのポインタ)
// [返り値]
//  アカウントリソース (Accountのポインタ)
//  エラー (error)
func (s *AccountServiceServer) DeleteAccount(ctx context.Context, req *pb.DeleteAccountRequest) (*emptypb.Empty, error) {
  log.Printf("[start]DeleteAccount req: %v\n", req)

  // アカウント情報削除処理をここへ書く

  log.Println("[end]DeleteAccount")
  return &emptypb.Empty{}, nil
}

3.2.4. main関数にgRPCサーバーの起動処理を記述する

指定のプロトコル(TCP)・ポート(5300)でgRPCサーバーを起動するようにする。
以下のファイルを作成した。

cmd/api/main.go
package main

import (
	"fmt"
	"log"
	"net"

  "google.golang.org/grpc"
	"google.golang.org/grpc/reflection"

	"github.com/<オーナー名>/sample_grpc_apis/pb"
	"github.com/<オーナー名>/sample_grpc_apis/service"
)

func main () {
	listenProtocol := "tcp"
	listenPortNum := 53000 // ポート番号
  
	// 1. 指定したプロトコル・ポートのListenerを作成
	// (net.Listenerが返される。Listenerとはポートに対して聞き耳を立てる人である)。
	portListener, err := net.Listen(listenProtocol, fmt.Sprintf(":%d", listenPortNum))
	// Listenに失敗したならばプログラムを即終了
	if err != nil {
		log.Fatalf("プロトコル:%v, ポート:%v のListener作成に失敗しました: %v", listenProtocol, listenPortNum, err)
	}

	// 2. gRPCサーバのインスタンスを生成
	// (grpc.Serverインスタンスのポインタが返ってくる)
	grpcServer := grpc.NewServer()

	// 3. gRPCサーバにサービスを登録
	// Register<サービス名>Server(grpc.Serverのポインタ, <サービス名>Serverのポインタ)関数は、
	// protocコマンド実行で生成された「<元になった.proroファイル名>_grpc.pb.go」内に自動で定義されている
	// ここで登録されたサービスについてのみAPIが使えるようになる
	pb.RegisterAccountServiceServer(grpcServer, &service.AccountServiceServer{})

	// 4. gRPCサーバのServer Reflectionを有効にする
	// (「grpc_cli」コマンドで、gRPCサーバに登録したサービスのRPCメソッドをシリアライズなしで実行可能になる)
	reflection.Register(grpcServer)

	// 5. gRPCサーバーを起動(指定したプロトコル・ポートのListenも開始)
	grpcServer.Serve(portListener)
}

3.2.5. 必要なモジュールをダウンロード

実装は終わったが、自動生成された*.pb.go, 作成したmain.go, account_service.goそれぞれで外部モジュールをインポートしてるので、
このままmain.goを起動しても動かず、以下のようにめちゃくちゃおこられる(当たり前)。

pb/account_grpc.pb.go:11:2: no required module provides package google.golang.org/grpc; to add it:
        go get google.golang.org/grpc
pb/account_grpc.pb.go:12:2: no required module provides package google.golang.org/grpc/codes; to add it:
        go get google.golang.org/grpc/codes
cmd/api/main.go:9:2: no required module provides package google.golang.org/grpc/reflection; to add it:
        go get google.golang.org/grpc/reflection
pb/account_grpc.pb.go:13:2: no required module provides package google.golang.org/grpc/status; to add it:
        go get google.golang.org/grpc/status
pb/account.pb.go:14:2: no required module provides package google.golang.org/protobuf/reflect/protoreflect; to add it:
        go get google.golang.org/protobuf/reflect/protoreflect
pb/account.pb.go:15:2: no required module provides package google.golang.org/protobuf/runtime/protoimpl; to add it:
        go get google.golang.org/protobuf/runtime/protoimpl
pb/account.pb.go:16:2: no required module provides package google.golang.org/protobuf/types/known/emptypb; to add it:
        go get google.golang.org/protobuf/types/known/emptypb

外部モジュールのダウンロードをする。

# コードをモジュール化(まだプロジェクトをモジュール化してないなら)
go mod init "github/<オーナー名>/sample_grpc/apis"

# コード内でimportしているモジュールをDL
go mod tidy

3.2.6. gRPCサーバーを起動

プロジェクトルートディレクトリで以下コマンドを実行する。

go run cmd/api/main.go

3.2.7. grpc_cliで実装の確認

前工程で、main.goでgRPCサーバーに対して「server reflection」という機能をしれっと有効化 していた。
これをやっておくと、「grpc_cli」 というコマンドラインツールを使い、クライアントからgRPCサーバーに対して以下の操作が可能になる。

  • 以下の情報の取得
    • 動作しているサービス名の一覧
    • 動作しているサービスに含まれるメソッド名、引数・返り値の型
    • 動作しているサービスに含まれるメソッドの引数や返り値の型の詳細
  • リクエストパラメータのシリアライズなしで各メソッドの動作確認

①grpc cliをインストールする

以下を参考に実施。

②各メソッドの動作確認

# 動作しているサービス名の表示
grpc_cli ls localhost:53000
=>
grpc.reflection.v1alpha.ServerReflection
xxxx.AccountService

# 指定したサービスのRPCメソッドを確認
grpc_cli ls localhost:53000 xxxx.AccountService -l
filename: proto/account.proto
package: xxxx;
service AccountService {
  rpc CreateAccount(xxxx.CreateAccountRequest) returns (xxxx.Account) {}
  rpc GetAccount(xxxx.GetAccountRequest) returns (xxxx.Account) {}
  rpc UpdateAccount(xxxx.UpdateAccountRequest) returns (xxxx.Account) {}
  rpc DeleteAccount(xxxx.DeleteAccountRequest) returns (google.protobuf.Empty) {}
}

# 指定したメッセージの型を確認
grpc_cli type localhost:53000 xxxx.CreateAccountRequest
message CreateAccountRequest {
  .xxxx.Account account = 1 [json_name = "account"];
}

# アカウント作成の動作確認
grpc_cli call localhost:53000 CreateAccount 'account: {id: "1", email: "test@example.com", password: "test"}'
=>
connecting to localhost:53000
id: "1"
email: "test@example.com"
password: "test"
Rpc succeeded with OK status

# アカウント情報取得の動作確認
grpc_cli call localhost:53000 GetAccount 'id: "1"'
=>
connecting to localhost:53000
id: "1"
email: "test@example.com"
password: "test"
Rpc succeeded with OK status

# アカウント情報更新の動作確認
grpc_cli call localhost:53000 UpdateAccount 'account: {id: "1", email: "test2@example.com", password: "testtest"}'
connecting to localhost:53000
id: "1"
email: "test2@example.com"
password: "testtest"
Rpc succeeded with OK status

# アカウント削除の動作確認
 grpc_cli call localhost:53000 DeleteAccount 'id: "1"'                                                    
connecting to localhost:53000
Rpc succeeded with OK status

まとめ

  • .protoのコーディング規約: gRPCでのAPI開発における一般的なやつ
    ここ

  • .protoファイルからprotoc(Goプラグイン gRPCプラグイン)によるビルドで生成される(_grpc.pb.go, pb.go)ファイル(= gRPCサービスのインターフェース定義 & リクエストパラメータのシリアライズ・デシリアライズ処理)を使った「gRPCサービスの実装」は、具体的にはどこをどうすればいいのか?
    ここ
    ※シリアライズ・デシリアライズ処理の方はいい感じにやってくれるっぽく、「サービスの構造体の定義」「サービスの構造体をレシーバとするメソッドの実装」だけすればよかった。

  • gRPCサーバーの起動に必要なmain関数への以下の記述ってどうすればいいのか?(どんな処理をどんな流れで書けばいいの?)

    • gRPCで使うプロトコル(TCPなど)・ポート番号の記述
    • gRPCサーバーへ処理対象とするgRPCサービスを登録
    • gRPCサーバーの起動
      ここ
  • gRPCサーバーを起動するにはどうしたらいいのか
    ここ

参考

.protoのコーディング規約系

※ファイルフォーマット(XXXケース、インデント、記載順など)について

※サービス・メッセージ・メッセージとして何を内部処理・内部データとして持たせるべきか

※サービス・メソッド・メッセージなどの名付けについて

※↑のドキュメントへのとっかかりを作ってくれたQiita記事

※リクエストメッセージ・レスポンスメッセージのフィールドとして何をもたせるべきか?のサンプルに。
コメントを参照すると意図がわかりやすい

.protocコマンド
https://maku.blog/p/37e6uck/

gRPC APIを実装するにあたってのプロジェクト構成

google.protobuf.Emptyを使えるようにする

ポートのListen

gRPCのServer Reflection

grpc_cliのインストール

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?