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公式ドキュメントを参考に、コーディング規約を定めてみた。
- Protocol Buffers - Developer Guide - Style Guide
- Cloud APIs - API設計ガイド - 標準メソッド
- Cloud APIs - API設計ガイド - 命名規則
注意:
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クエリパラメータ: その他の情報
- URLパス: 紐付ける親リソースの指定(
- 表示:
- メソッド名: Get<リソース名>
- 引数の型: Get<リソース名>Request
- 返り値の型: <リソース名>
- HTTPメソッド: GET
- リクエストパラメータの受け取り方:
- URLパス: 表示対象の「自身のリソースを特定するための親リソースを含めたパス」(
<親リソース名>s/<親リソースの識別子の値>/<子リソース名>s/<子リソースの識別子の値>) - リクエストボディ: なし
- URLクエリパラメータ: その他の情報
- URLパス: 表示対象の「自身のリソースを特定するための親リソースを含めたパス」(
- 一覧表示:
- メソッド名: List<リソース名>s
- 引数の型: List<リソース名>sRequest
- 返り値の型: List<リソース名>sResponse
- HTTPメソッド: GET
- リクエストパラメータの受け取り方:
- URLパス: 表示対象の「自身のリソース群が紐づく、親のリソースを特定するためのパス」(
<親リソース名>/<親リソースの識別子の値>) - リクエストボディ: なし
- URLクエリパラメータ: その他の情報(自身のリソースの情報をいくつ取得するか?など)
- URLパス: 表示対象の「自身のリソース群が紐づく、親のリソースを特定するためのパス」(
- 更新:
- メソッド名: Update<リソース名>
- 引数の型: Update<リソース名>Request
- 返り値の型: <リソース名>
- HTTPメソッド: PATCH ※完全な更新(が何を指すかはわからないが)のみとする場合はPUT。そもそも完全な更新は非推奨とのこと
- リクエストパラメータの受け取り方:
- URLパス: 更新対象の「自身のリソースを特定するための親リソースを含めたパス」(
<親リソース名>s/<親リソースの識別子の値>/<子リソース名>s/<子リソースの識別子の値>) - リクエストボディ: (更新後の)リソース情報
- URLクエリパラメータ: その他の情報
- URLパス: 更新対象の「自身のリソースを特定するための親リソースを含めたパス」(
- 削除:
- メソッド名: Delete<リソース名>
- 引数の型: Delete<リソース名>Request
- 返り値の型:
google.protobuf.Empty - HTTPメソッド: DELETE
- リクエストパラメータの受け取り方:
- URLパス: 削除対象の「自身のリソースを特定するための親リソースを含めたパス」(
<親リソース名>s/<親リソースの識別子の値>/<子リソース名>s/<子リソースの識別子の値>) - リクエストボディ: なし
- URLクエリパラメータ: その他の情報
- 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. の外部設計を踏まえて、以下の工程を実施する。
-
.protoファイルの作成 -
.protoファイルから「gRPCサービスインターフェース定義ファイル(_grpc.pb.go)」と「メッセージ定義ファイル(.pg.go)」を生成 -
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ファイルを作成する
// 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 が出力される。
それぞれの中身は以下の通り
// 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
}
// 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ファイルを作成し、実装していく
インターフェース
...
// 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()
}
実装
// アカウント関連サービスの実装
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サーバーを起動するようにする。
以下のファイルを作成した。
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のインストール