はじめに
現代のバックエンド開発では、柔軟性があり高速な通信プロトコルが求められています。
そこで注目されるのが、Googleが開発したgRPCという通信技術です。gRPCは、HTTP/2を使って、低遅延かつ効率的にクライアントとサーバー間でのデータ通信を行います。また、バイナリ形式のProtocol Buffersを用いることで、JSONやXMLなどに比べて非常に軽量な通信を実現します。
しかし、従来のgRPCはブラウザ対応の難しさや、認証・HTTPサポートの実装コストが課題でした。ここで登場するのが、今回使用する Connect-goです。
Connect-goは、gRPCだけでなくgRPC-WebやHTTP/JSON通信にも対応しており、シンプルで拡張性の高いAPI構築を支援するフレームワークです。
本記事では、Connect-goを使ったgRPCサービスの構築手順を、公式ドキュメントに沿って実践します。モジュールの初期化から、Protocol Buffersを用いたサービス定義、Bufによるコード生成、Goサーバーの実装まで、一連のプロセスを順を追って進めていきます。
ハンズオン
公式ドキュメント
Goプロジェクトの作成
gRPCサービスを構築するため、まずGoプロジェクトを初期化します。
Goでは、プロジェクトの依存関係やモジュール管理にgo modを使用します。
(go mod init [プロジェクト名])
go mod init example
ディレクトリとprotoファイルの作成
gRPCサービスで使用する Protocol Buffers(.proto)ファイル を作成します。
このファイルには、APIのリクエスト・レスポンスの形式や、RPCメソッドの定義を記述します。
mkdir -p greet/v1
touch greet/v1/greet.proto
greet.protoの定義
次に、greet.protoにgRPCサービスの定義を書き込みます。
greet.protoファイルでは、リクエスト・レスポンスのメッセージ形式と、それを使うサービスメソッドを定義します。
greet.protoの内容
syntax = "proto3";
package greet.v1;
option go_package = "example/gen/greet/v1;greetv1";
message GreetRequest {
string name = 1;
}
message GreetResponse {
string greeting = 1;
}
service GreetService {
rpc Greet(GreetRequest) returns (GreetResponse) {}
}
解説
-
syntax = "proto3";-
Proto3はProtocol Buffersの最新バージョンで、シンプルかつ軽量なデータ形式で定義を行います。
-
-
package greet.v1;- パッケージは、
APIを論理的に分割し、命名の競合を避けるために使います。ここではgreetのバージョン1(v1)を表しています。
- パッケージは、
-
option go_package- 生成される
Goコードの出力先を指定します。example/gen/greet/v1;greetv1の形式で、greetv1という名前でGoパッケージとして利用できるようになります。
- 生成される
-
message GreetRequest- リクエストの形式を表すメッセージです。ここでは、
nameという文字列を含む構造体を定義しています。
- リクエストの形式を表すメッセージです。ここでは、
-
message GreetResponse- レスポンスの形式を表すメッセージです。
greetingという文字列を返します。
- レスポンスの形式を表すメッセージです。
-
service GreetService-
gRPCのサービスを定義するブロックです。 -
rpc GreetはリクエストとしてGreetRequestを受け取り、GreetResponseを返すRPCメソッドです。
-
Bufモジュールの初期化
次に、Bufを使ってモジュールを初期化します。
これにより、buf.yamlという設定ファイルが生成されます。
buf mod init
buf.yamlの解説
buf mod initコマンドを実行すると、次のようなbuf.yamlが生成されます。
# For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml
version: v2
lint:
use:
- STANDARD
breaking:
use:
- FILE
解説
-
version: v2- Bufの設定ファイルのバージョンを示します。
- Bufのルールセットや構文チェックの対象が、このバージョンに基づいて動作します。
- lintセクション
use: - STANDARD- Bufが推奨する 「標準的なLintルール」 を適用します。
- これにより、Protoファイルの構文エラーやフォーマットのミスを検出し、プロジェクトの品質を保ちます。
- breakingセクション
use: - FILE- ファイル単位でのBreaking Change(互換性を壊す変更) を検出します。
- 例えば、ProtoファイルからメッセージやRPCメソッドを削除するなどの変更を検出し、意図せずAPIが壊れるのを防ぎます。
Bufを使ったProtoファイルのチェック
buf.yamlファイルが設定された状態で、lintチェックやビルドを行えます。
buf lint
このコマンドを実行すると、greet.protoの構文やスタイルに問題がないかBufがチェックします。
buf build
このコマンドで、BufがProtoファイルをビルドし、エラーがないか確認します。
buf.gen.yamlの作成
まず、コード生成のためのbuf.gen.yamlを作成します。
touch buf.gen.yaml
buf.gen.yaml の内容
(環境に合わせて調整してください)
version: v2
plugins:
- local: protoc-gen-go
out: gen
opt: paths=source_relative
- local: protoc-gen-connect-go
out: gen
opt: paths=source_relative
解説
-
version: v2- Bufの設定が最新のv2形式であることを示します。
-
plugins- local:ローカルにインストールされたプラグインを使用します。
-
protoc-gen-go:Goのコードを生成するためのプラグイン。 -
protoc-gen-connect-go:Connect-goのためのハンドラコードを生成します。
-
- local:ローカルにインストールされたプラグインを使用します。
-
out: gen- 生成されたコードの出力先を
gen/ディレクトリに指定します。
- 生成されたコードの出力先を
-
opt: paths=source_relative- ファイルが相対パスで生成され、ソースコード内のパッケージ構造に合致するようにします。
コード生成の実行
greet.protoからGoコードを生成します。
buf generate
gRPCサービスを実装するためのコード生成が完了しました。
生成されるコードの内容
greet.proto に基づいて以下の2種類のコードが生成されます。
-
protoc-gen-goによるコード生成
ファイル: /gen/greet/v1/greet.pb.go
役割:gRPCのメッセージ型やサービスの定義をGo用の構造体として生成します。
GreetRequestやGreetResponseがGoの型として使えるようになります。
gRPCサーバー側で使われるサービスインターフェースも定義されます。
-
protoc-gen-connect-goによるコード生成
ファイル: /gen/greet/v1/greetv1connect/greet.connect.go
役割:Connect-go用のハンドラコードを生成します。
GreetServiceのサービスハンドラが自動生成され、Goサーバーで使うRPCエンドポイントを簡単にセットアップできます。
Connect-goは、gRPCだけでなく、gRPC-WebやHTTP/JSONにも対応した柔軟な通信をサポートします。
Goサーバーの作成
生成された gRPCメッセージ・ハンドラコードを使って、Goサーバーを実装します。
このサーバーは、Connect-goを使ってgRPCリクエストを受け取り、HTTP/2で通信を行います。
ディレクトリ構成
次のように、サーバーのエントリーポイントとなるGoファイルを作成します。
mkdir -p cmd/server
touch cmd/server/main.go
Goサーバー実装:cmd/server/main.go
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/bufbuild/connect-go" // 注意:公式では"connectrpc.com/connect"となっていますが、動作しないです。
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
greetv1 "example/gen/greet/v1" // generated by protoc-gen-go
"example/gen/greet/v1/greetv1connect" // generated by protoc-gen-connect-go
)
type GreetServer struct{}
func (s *GreetServer) Greet(
ctx context.Context,
req *connect.Request[greetv1.GreetRequest],
) (*connect.Response[greetv1.GreetResponse], error) {
log.Println("Request headers: ", req.Header())
res := connect.NewResponse(&greetv1.GreetResponse{
Greeting: fmt.Sprintf("Hello, %s!", req.Msg.Name),
})
res.Header().Set("Greet-Version", "v1")
return res, nil
}
func main() {
greeter := &GreetServer{}
mux := http.NewServeMux()
path, handler := greetv1connect.NewGreetServiceHandler(greeter)
mux.Handle(path, handler)
http.ListenAndServe(
"localhost:8080",
// Use h2c so we can serve HTTP/2 without TLS.
h2c.NewHandler(mux, &http2.Server{}),
)
}
解説
-
GreetServer構造体-
GreetServerは、GreetServiceのメソッドを実装する構造体です。 -
Greetメソッドで、リクエストからユーザー名を取得し、Hello, [name]!という挨拶をレスポンスとして返します。
-
-
connect-goの使用-
github.com/bufbuild/connect-goをインポートして使います。公式ではconnectrpc.com/connectというパスが推奨されていますが、互換性の問題があるためbufbuildのパスを使います。
-
-
h2cを使ったHTTP/2の起動
-
h2c.NewHandlerを使い、TLSなしでHTTP/2サーバーを起動しています。 -
gRPCはHTTP/2をベースとしているため、この設定が必要です。
- HTTPハンドラへのサービス登録
-
greetv1connect.NewGreetServiceHandlerを使って、GreetServiceをHTTPハンドラとして登録します。 - このハンドラは、
gRPC-WebやHTTP/JSONにも対応できます。
-
go mod tidy
最後に
go mod tidy
をしてください。
サーバーの実行とテスト
go runコマンドでサーバーを起動し、curlとgrpcurlを使ってサービスが正しく応答するか確認します。
サーバーの起動
go run ./cmd/server/main.go
curlでのテスト
curlを使って、サービスにリクエストを送信し、JSONレスポンスが正常に返ってくるか確認します。
curl \
--header "Content-Type: application/json" \
--data '{"name": "Jane"}' \
http://localhost:8080/greet.v1.GreetService/Greet
実行結果
{
"greeting": "Hello, Jane!"
}
grpcurlでのテスト
grpcurlを使ってgRPC形式でリクエストを送信します。
grpcurl \
-protoset <(buf build -o -) -plaintext \
-d '{"name": "Jane"}' \
localhost:8080 greet.v1.GreetService/Greet
実行結果
{
"greeting": "Hello, Jane!"
}
最後に
Connect-goを使ったgRPCサービスの構築は、シンプルで拡張性の高いバックエンド開発を実現する優れた選択肢です。本記事では、
公式ドキュメントに沿ってサービスの定義、コード生成、サーバー実装からAPIテストまでの流れを解説しました。今回のハンズオンを通じて、gRPCとHTTP/JSONを同一エンドポイントで提供する実装の柔軟さを実感していただけたかと思います。
最後に、本記事で使用した全コードを以下にまとめておきます。各ステップを順に進め、実装が意図通り動作するかぜひ確認してみてください。