7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

gRPCってなに?サーバーを作りながら理解してみよう

Last updated at Posted at 2024-10-20

株式会社Schoo 新卒1年目の @hiroto_0411です!
最近、業務でgRPCサーバーの実装を行うことになったのですが、gRPCに触れたことがなかったのでgRPCのサーバーを作りながら概要と実装方法をまとめてみました!

この記事でわかること

  • gRPCってなに?
  • gRPCサーバーを作る流れ

gRPCとは

Googleが開発したオープンソースのRPCフレームワーク

RPCとは

Remote Procedure Callの略。直訳すると遠隔手続き呼び出し。クライアントから別のサーバーの関数・メソッドにリクエストを送り、そのレスポンスを受け取ること。別のサーバーにある関数・メソッドを呼び出して使うようなイメージ。

gRPCのイメージ
スクリーンショット 2024-10-17 0.44.45.png

REST APIと何が違うの?と思ったので調べてみた

RESTがリソース(データなどのサービスが提供しているもの)に対してHTTPメソッド(GET,POST,PUT,DELETEなど)で操作するという思想であるのに対し、RPCはメソッドを呼び出して特定の処理を行うという思想である。
この違いは、それぞれのAPIにリクエストを送るためのURIをみると分かりやすいかなと思った。

RPC

http://localhost:8080/registerUser

ユーザー登録の関数を呼び出している感じ。

REST

POST http://localhost:8080/users

usersというリソースに対してPOSTで登録している感じ。

思想以外にも、通信に使うプロトコルやデータの送受信方法などの違いはあるが、データの送受信を行うAPIを実装できるという点では同じである。

gRPCの特徴

  • 送信するデータをProtocol Buffersというフォーマットを使用してバイナリデータに変換するため、軽量で高速なやりとりができる
  • HTTP/2を利用した通信を行うことで、1つの接続で同時に複数のリクエストを処理できたり、効率の良いバイナリデータの転送ができる
  • 1つのリクエストまたはレスポンスを分割して小さなチャンクで順次送受信するストリーミング通信ができる

gRPCサーバーを作ってみる

以下のQuick startを参考にしながらgRPCサーバーを作ってみた。今回はGoを使っていますが、流れは他の言語であっても大きくは変わらない。

作成したコードは以下で見られる。

ディレクトリ構成はこんな感じになる予定。

.
├── gen // helloworld.protoから自動生成される
│   ├── helloworld_grpc.pb.go
│   └── helloworld.pb.go
├── proto
│   └── helloworld.proto
├── server // 具体的な処理を書く。今回は{"message": "Hello Name"}を返すための実装を記載。
│   └── helloworld.go
├── buf.gen.yaml //.protoファイルからコードを自動生成するための設定を書く
├── buf.yaml
├── go.mod
├── go.sum
└── main.go

1. 準備

モジュールの作成

go mod init github.com/user-name/project-name   

コード生成に必要なツールのインストール

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install github.com/bufbuild/buf/cmd/buf@latest

PATHを更新

export PATH="$PATH:$(go env GOPATH)/bin"

2. .protoファイルの作成

Protocol Buffersというフォーマットを使用してgRPCのサービス名やメソッド名、リクエストやレスポンスを定義する。

./proto/helloworld.proto
syntax = "proto3";

option go_package = "github.com/user-name/project-name/gen";

package hello;

service Greeter {
    rpc SayHello(HelloRequest) returns (HelloResponse){ }
}

message HelloRequest {
    string name = 1;
}

message HelloResponse {
    string message = 1;
}


この後、.protoファイルを元にコードを自動生成する。その時に出力したいpackage名をgo_packageで指定する。package hello;はGoのpackage名とは関係ない。このpackageは名前が衝突するのを防ぐために使われる。

package user;

message Person {
    string name = 1;
    int32 age = 2;
}
package employee;

message Person {
    string name = 1;
    string employee_id = 2;
}

このように同じ名前のserviceやmessageを定義したときに問題が発生しないように、package名を指定する。

3. コードの自動生成

buf.yamlファイルをルートディレクトリに作成する
buf.yamlにはどのディレクトリの.protoファイルをコンパイルするかや、linterなどの設定、リモートのプロジェクトやサードパーティのProtobufファイルを参照するための依存関係の設定などを記述する。(今回は生成されたものをそのまま使う。)

buf config init

buf.gen.yamlをルートディレクトリに作成する

./buf.gen.yaml
version: v2
plugins:
  - local: protoc-gen-go
    out: gen
    opt:
      - module=github.com/user-name/project-name/gen
  - local: protoc-gen-go-grpc
    out: gen
    opt: 
      - module=github.com/user-name/project-name/gen

コードを自動生成する
以下のコマンドを実行することで、.protoファイルとbuf.gen.yamlを元に、XXX_grpc.pg.goとXXX.pb.goの2種類のファイルが自動生成される。

buf generate

XXX_grpc.pg.go
https://github.com/hiroto1220/go-playground/blob/main/gRPC/gen/helloworld.pb.go

サーバー(やクライアント)が.protoファイルのサービスに定義したメソッドを実装するためのインターフェースが含まれる。このインターフェースを使い、それぞれのサービスの実装を行う。

XXX.pb.go
https://github.com/hiroto1220/go-playground/blob/main/gRPC/gen/helloworld_grpc.pb.go
.proto ファイルで定義されたメッセージ・タイプと基本的なデータ構造のコードが含まれている。メソッドを呼び出したり実装したりする際には、これらの型を使用してリクエストオブジェクトとレスポンスオブジェクトを構築する。

4. 自動生成されたinterfaceを使い、サーバーを実装する

./gen/helloworld_grpc.pb.go
type GreeterServer interface {
	SayHello(context.Context, *HelloRequest) (*HelloResponse, error)
	mustEmbedUnimplementedGreeterServer()
}

type UnimplementedGreeterServer struct{}

func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloResponse, error) {
	return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
./server/helloworld.go
package server

import (
	"context"
	"log"

	pb "github.com/user-name/project-name/gen"
)

type GreeterServer struct {
	pb.UnimplementedGreeterServer
}

// ./gen/helloworld_grpc.pb.goに定義されているGreeterServerインターフェースを実装する
func (s *GreeterServer) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
	log.Printf("Received: %v", in.GetName())

	// 本来ならここから、別の層のメソッドを呼び出すなどする

	return &pb.HelloResponse{Message: "Hello " + in.GetName()}, nil
}

./gen/helloworld_grpc.pb.goに定義されているGreeterServerインターフェースを実装し、それをmain.goで使用する。

./main.go
package main

import (
	"flag"
	"fmt"
	"log"
	"net"

	pb "github.com/user-name/project-name/gen"
	"github.com/user-name/project-name/server"

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

var (
	port = flag.Int("port", 50051, "The server port")
)

func main() {
	flag.Parse()
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server.GreeterServer{})
	log.Printf("server listening at %v", lis.Addr())

	reflection.Register(s)

	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

依存関係の整理

go mod tidy

5. サーバーにリクエストを送ってみる

サーバーの起動

go run main.go

grpcurlをインストール

brew install grpcurl

The main purpose for this tool is to invoke RPC methods on a gRPC server from the command-line. gRPC servers use a binary encoding on the wire (protocol buffers, or "protobufs" for short). So they are basically impossible to interact with using regular curl

gRPCurlはコマンドラインからgRPCサーバーを呼び出すツールである。curlはテキストベースのHTTPリクエストに対応しているが、バイナリ形式のProtocol Buffersには対応していない。また、ストリーミングにも対応していない。そのためコマンドラインからcurlで呼び出すことができない。(Connectを使う、grpc-gatewayを使うなど実装方法を変えるとcurlで通信できるようにもなる。)

リクエストを送信

 grpcurl -plaintext -d '{"name": "パブりん"}' localhost:50051 hello.Greeter/SayHello
{
  "message": "Hello パブりん"
}


grpc-gatewayの記事も書いているのでぜひご覧ください!

参考


Schooでは一緒に働く仲間を募集しています!

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?