0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GoでgRPC API超入門

Last updated at Posted at 2024-07-31

自分の中で少しずつ理解できてきたので、小分けにして整理していきたくこの記事を書きました。
同じ初学者のお役に立てれば。

当記事は実装メインで解説します。
protoって何の略?これは内部でどうなってるの?的な細かい話は省いています。
気になるところがあったら他記事で補完してください🙏

構成

今回は最小限の超簡単な構成とします。
image.png
初学者はHello,World!を出力しなければならないという法律に則り、Hello,World!を出力するAPIを実装していきます。

実装では、クライアントのインスタンス、gRPCサーバのインスタンスをそれぞれ立ち上げ、リクエスト〜レスポンスまでの流れを確認する、ということをやります。

大まかな手順

ゆるふわ概念図を記しておきます。

全体像

まずは作成するものの全体像。さっきのクライアント↔︎gRPCサーバの図をもうちょい細かく︎書いた図です。
image.png
※クライアント(インスタンス)、gRPCサーバ(インスタンス)はgoファイルをコマンド実行している間だけ生きています。一方、pb.goファイルは一度作成されたらずっとそこにあります。

ざっくり手順 〜3ステップ〜

  1. protoファイルというものを作って、コマンドを実行します。このコマンドでprotoファイルを元としたファイルが生成されます。そのあと、サーバ、クライアント側の実装をします。
    grpc go ファイル作成.gif

  2. 次に、コマンド実行でgRPCサーバを作成します。
    grpc go ファイル作成2.gif

  3. 最後に、クライアントのインスタンスを生成し、gRPC APIを叩けば完成!
    grpc go ファイル作成3.gif

上の図より、初手で作るファイルは以下の3つになります。

  • server.go // サーバの実装
  • client.go // クライアント側の実装
  • hello.proto // APIの細かいリクエスト・レスポンス内容の実装

図でみると、↓の赤枠のところですね。
image.png

フォルダ構成

1つのフォルダ直下に全てをぶっ込みます。フォルダ構成が簡単だとテンション上がるので。

grpc-test/
┝ server.go
┝ client.go
└ hello.proto

いざ実装!の前に

GitHubに空のリポジトリ「grpc-test」を作っておき、
このリポジトリをローカルに持ってきた上で、
「grpc-test」直下に諸々のファイルを作っていく
という流れをやっておきます。
(pb.goファイルのURL指定のため)

hello.protoの実装

まずは第一にこれを実装します。なぜならこのprotoファイルにリクエスト・レスポンスの全てをぶち詰め込むからです。goでのgrpcはこの書き方が一般的です。

syntax = "proto3";

package hello;

// このファイルにより生成されるpb.goファイルを置く(出力する)ディレクトリ
option go_package = "/pb";


service HelloService {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

コード中
option go_package = "/pb";
の部分は、pb.goファイルが生成される場所になります。
今回、上記のように指定したことで、grpc-test直下にpbファイルが作られることが確定しました。他のパスに置きたければここを編集すればOK。

次の項「pb.goファイルの生成」を実行したあとは、こんな配置になるはず。

grpc-test/
┝ pb/
|  ┝ hello.pb.go
|  └ hello_grpc.pb.go
(略)

pb.goファイルの生成

  1. ターミナルを開き、今回のルート「grpc-test」フォルダ直下に移動します。
  2. 以下のコマンドを実行します。
    protoc --go_out=. --go-grpc_out=. hello.proto
    
  3. 「pb」フォルダが作成され、直下に「hello.pb.go」「hello_grpc.pb.go」が作成されています。

現時点でのフォルダ構成は以下のようになっていると思います。

grpc-test/
┝ pb/
|  ┝ hello.pb.go
|  └ hello_grpc.pb.go
┝ server.go
┝ client.go
└ hello.proto

protoファイル作成、pb.goファイル作成が完了しました。
ここで先ほどの図を振り返ってみます。今回やったのは↓の部分にあたります。
image.png

server.go の実装

go server.go
package main

import (
  "context"
  "log"
  "net"

  "google.golang.org/grpc"
  pb "github.com/<your_user_name>/grpc-test/pb" // 後ほど生成されたhello.pb.goとhello_grpc.pb.goが置いてあるパスに変更
)

type server struct {
  pb.UnimplementedHelloServiceServer
}

// このSayHelloメソッドがサービスハンドラーの役割を果たす
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
  return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
  lis, err := net.Listen("tcp", ":50051") // リスナー作成
  if err != nil {
    log.Fatalf("failed to listen: %v", err)
  }
  s := grpc.NewServer() // gRPCサーバーのインスタンスを作成
  pb.RegisterHelloServiceServer(s, &server{}) // HelloServiceサービスをサーバーに登録
  log.Printf("server listening at %v", lis.Addr())
  if err := s.Serve(lis); err != nil {
    log.Fatalf("failed to serve: %v", err)
  }
}

先ほどの図では↓の部分にあたります。
image.png

ここで

go mod init
go mod tidy

コマンドを実行して、go.modgo.sum 兄弟をつくっておきます。

go run server.goコマンドを打つとgRPCサーバがつくられます。
image.png
start listening at で返されればOK。ctrl+Cで抜けれます。

(別に読まなくていい補足)
RPCではHTTPルーティングのようなURLベースのルーティングはなく、
protoファイルで定義されたサービスとメソッドがルーティングの役割を果たします。
今回は、SayHelloメソッドがHelloServiceサービスにルーティングされるようになります。

client.goの実装

go client.go
package main

import (
  "context"
  "log"
  "os"
  "time"

  "google.golang.org/grpc"
  pb "xxx" // 生成されたhello.pb.goとhello_grpc.pb.goのパッケージパスに変更
)

func main() {
  conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
  if err != nil {
    log.Fatalf("did not connect: %v", err)
  }
  defer conn.Close()
  c := pb.NewHelloServiceClient(conn)

  name := "world"
  if len(os.Args) > 1 {
    name = os.Args[1]
  }

  ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  defer cancel()

  r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
  if err != nil {
    log.Fatalf("could not greet: %v", err)
  }
  log.Printf("Greeting: %s", r.GetMessage())
}

ここで

go mod tidy

コマンドを実行して、go.modgo.sum を更新しておきます。

先ほどの図では↓の部分にあたります。
リクエスト送信、レスポンス受け取り&表示までが、クライアント側で実装した内容になります。

image.png

main関数 func main() が同じ階層に2つ以上存在する場合、警告が出るっぽいです。
mainの実行自体はできるので、今回は警告を無視して進めます。

(別に読まなくていい補足)
この実装したクライアントは、リモートプロシージャコール(RPC)という通信方法でリクエストを送信します。
先ほど作成したserver.goのサーバも、リモートプロシージャコール(RPC)という通信を受け取り、処理します。

クライアントからリクエストをgRPCサーバに投げてみる

ここまでの実装で、やっと完成です。
フォルダはこんな感じになってるはず。

grpc-test/
┝ pb/
|  ┝ hello.pb.go
|  └ hello_grpc.pb.go
┝ go.mod
┝ go.sum
┝ server.go
┝ client.go
└ hello.proto

それでは、早速やってみます。

順序はこれだけ。

  1. gRPCサーバを立ち上げる
  2. クライアントからリクエストを投げる

1. gRPCサーバを立ち上げる

「grpc-test」リポジトリ直下に行きます。
ターミナルを立ち上げ、以下のコマンドを実行します。

go run server.go

これで start listening at <port番号>のような形で返されればOKです。
ctrl+Cで抜けれます。
image.png

2. クライアントからリクエストを投げる

「grpc-test」リポジトリ直下に行きます。
先ほどサーバを起動したターミナルとは別のターミナルを立ち上げ、以下のコマンドを実行します。

go run client.go

これで Greeting: Hello worldと返されました!

ちなみに、

go run client.go hoge

を実行すると、Greeting: Hello hogeと返されます。

gRPC APIのつくりかた まとめ

protoファイルを作ってpb.goを生成する。そのあと、サーバ、クライアント側の実装をする。
grpc go ファイル作成.gif

これでファイルは完成。

次に、go run server.go コマンドでgRPCサーバを作成。
grpc go ファイル作成2.gif

最後に、クライアントのインスタンスを生成し、gRPC APIを叩く。
すると、以下のようなルートを通り、レスポンスが返されます。
grpc go ファイル作成3.gif

リクエスト、レスポンスの中身はgRPCサーバ内でいろいろと加工されたりします。

厳密には違う部分もあるかもですが、ざっくりこんな感じです。
ご指摘あれば何なりと!

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?