1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Connect-go ハンズオン

Posted at

はじめに

現代のバックエンド開発では、柔軟性があり高速な通信プロトコルが求められています。
そこで注目されるのが、Googleが開発したgRPCという通信技術です。gRPCは、HTTP/2を使って、低遅延かつ効率的にクライアントとサーバー間でのデータ通信を行います。また、バイナリ形式のProtocol Buffersを用いることで、JSONXMLなどに比べて非常に軽量な通信を実現します。

しかし、従来のgRPCはブラウザ対応の難しさや、認証・HTTPサポートの実装コストが課題でした。ここで登場するのが、今回使用する Connect-goです。
Connect-goは、gRPCだけでなくgRPC-WebHTTP/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.protogRPCサービスの定義を書き込みます。
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) {}
}

解説

  1. syntax = "proto3";

    • Proto3Protocol Buffersの最新バージョンで、シンプルかつ軽量なデータ形式で定義を行います。
  2. package greet.v1;

    • パッケージは、APIを論理的に分割し、命名の競合を避けるために使います。ここではgreetのバージョン1(v1)を表しています。
  3. option go_package

    • 生成されるGoコードの出力先を指定します。example/gen/greet/v1;greetv1の形式で、greetv1という名前でGoパッケージとして利用できるようになります。
  4. message GreetRequest

    • リクエストの形式を表すメッセージです。ここでは、nameという文字列を含む構造体を定義しています。
  5. message GreetResponse

    • レスポンスの形式を表すメッセージです。greetingという文字列を返します。
  6. 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

解説

  1. version: v2
    • Bufの設定ファイルのバージョンを示します。
    • Bufのルールセットや構文チェックの対象が、このバージョンに基づいて動作します。
  2. lintセクション use: - STANDARD
    • Bufが推奨する 「標準的なLintルール」 を適用します。
    • これにより、Protoファイルの構文エラーやフォーマットのミスを検出し、プロジェクトの品質を保ちます。
  3. breakingセクション use: - FILE
    • ファイル単位でのBreaking Change(互換性を壊す変更) を検出します。
    • 例えば、ProtoファイルからメッセージやRPCメソッドを削除するなどの変更を検出し、意図せずAPIが壊れるのを防ぎます。

Bufを使ったProtoファイルのチェック

buf.yamlファイルが設定された状態で、lintチェックやビルドを行えます。

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

解説

  1. version: v2

    • Bufの設定が最新のv2形式であることを示します。
  2. plugins

    • local:ローカルにインストールされたプラグインを使用します。
      • protoc-gen-go:Goのコードを生成するためのプラグイン。
      • protoc-gen-connect-go:Connect-goのためのハンドラコードを生成します。
  3. out: gen

    • 生成されたコードの出力先をgen/ディレクトリに指定します。
  4. opt: paths=source_relative

    • ファイルが相対パスで生成され、ソースコード内のパッケージ構造に合致するようにします。

コード生成の実行

greet.protoからGoコードを生成します。

buf generate

gRPCサービスを実装するためのコード生成が完了しました。

生成されるコードの内容

greet.proto に基づいて以下の2種類のコードが生成されます。

  1. protoc-gen-goによるコード生成

ファイル: /gen/greet/v1/greet.pb.go

役割:gRPCのメッセージ型やサービスの定義をGo用の構造体として生成します。
GreetRequestGreetResponseがGoの型として使えるようになります。
gRPCサーバー側で使われるサービスインターフェースも定義されます。

  1. protoc-gen-connect-goによるコード生成

ファイル: /gen/greet/v1/greetv1connect/greet.connect.go

役割:Connect-go用のハンドラコードを生成します。
GreetServiceのサービスハンドラが自動生成され、Goサーバーで使うRPCエンドポイントを簡単にセットアップできます。
Connect-goは、gRPCだけでなく、gRPC-WebHTTP/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{}),
	)
}

解説

  1. GreetServer構造体

    • GreetServerは、GreetServiceのメソッドを実装する構造体です。
    • Greetメソッドで、リクエストからユーザー名を取得し、Hello, [name]!という挨拶をレスポンスとして返します。
  2. connect-goの使用

    • github.com/bufbuild/connect-goをインポートして使います。公式ではconnectrpc.com/connectというパスが推奨されていますが、互換性の問題があるためbufbuildのパスを使います。
  3. h2cを使ったHTTP/2の起動

  • h2c.NewHandlerを使い、TLSなしでHTTP/2サーバーを起動しています。
  • gRPCHTTP/2をベースとしているため、この設定が必要です。
  1. HTTPハンドラへのサービス登録
    • greetv1connect.NewGreetServiceHandlerを使って、GreetServiceをHTTPハンドラとして登録します。
    • このハンドラは、gRPC-WebHTTP/JSONにも対応できます。

go mod tidy

最後に

go mod tidy

をしてください。

サーバーの実行とテスト

go runコマンドでサーバーを起動し、curlgrpcurlを使ってサービスが正しく応答するか確認します。

サーバーの起動

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テストまでの流れを解説しました。今回のハンズオンを通じて、gRPCHTTP/JSONを同一エンドポイントで提供する実装の柔軟さを実感していただけたかと思います。

最後に、本記事で使用した全コードを以下にまとめておきます。各ステップを順に進め、実装が意図通り動作するかぜひ確認してみてください。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?