connect-go(Getting Started)
connect-go
新しいGoモジュールを作成し、いくつかのコード生成ツールをインストールする
mkdir connect-go-example
cd connect-go-example
go mod init example
go install github.com/bufbuild/buf/cmd/buf@latest
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install github.com/bufbuild/connect-go/cmd/protoc-gen-connect-go@latest
Goのインストールディレクトリをパスに追加する:
[ -n "$(go env GOBIN)" ] && export PATH="$(go env GOBIN):${PATH}"
[ -n "$(go env GOPATH)" ] && export PATH="$(go env GOPATH)/bin:${PATH}"
protoファイルを定義する
mkdir -p greet/v1
touch greet/v1/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) {}
}
このprotoファイルは、greet.v1パッケージ内にある、GreetServiceという名前のサービスを定義しています。proto3構文を使用しています。
まず、syntax = "proto3";
という行は、このprotoファイルがproto3構文を使用していることを示しています。
次に、package greet.v1;
という行は、このprotoファイルがgreet.v1パッケージに所属していることを示しています。
option go_package = "example/gen/greet/v1;greetv1";
という行は、生成されるGoコードのパッケージ名とインポートパスを指定しています。具体的には、生成されるGoコードのパッケージはexample/gen/greet/v1
となり、インポートパスにはgreetv1
が使用されます。
message GreetRequest
という行から始まるブロックは、GreetRequestというメッセージ型を定義しています。このメッセージ型は1つのフィールドを持ち、そのフィールドはstring型のnameで、タグ番号は1です。
同様に、message GreetResponse
という行から始まるブロックは、GreetResponseというメッセージ型を定義しています。このメッセージ型も1つのフィールドを持ち、そのフィールドはstring型のgreetingで、タグ番号は1です。
最後に、service GreetService
という行から始まるブロックは、GreetServiceという名前のサービスを定義しています。このサービスは1つのメソッドを持ち、そのメソッドはGreetRequestを受け取り、GreetResponseを返します。メソッド名はGreetです。
以上がこのprotoファイルの内容です。このファイルは、gRPCなどのプロトコルバッファコンパイラを使用して、各言語に対応したコードを生成するために使用されることがあります。
buf.yamlを作成する
buf mod init
buf.yamlが生成される
次に、buf.gen.yamlを次のように作成する
version: v1
plugins:
- plugin: go
out: gen
opt: paths=source_relative
- plugin: connect-go
out: gen
opt: paths=source_relative
buf lint
,buf generate
コマンドは、protoファイルを変更するたびに実行します
buf lint
buf lint
コマンドを実行すると、Protocol Buffersのソースコードに対して様々なLintルールが適用され、問題がある場合にはそれらが報告されます。
buf generate
buf generate
コマンドを実行すると、指定された定義ファイルから生成されたコードが出力されます。生成されたコードは、各言語のベストプラクティスに従って設計されており、Protocol Buffersを使用するアプリケーション開発を簡素化します。
サーバーを実装する
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/bufbuild/connect-go"
"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{}),
)
}
このmain.go
ファイルは、Protocol Buffersのgreet.protoファイルで定義されたGreetServiceの実装を含む、Go言語のサーバーアプリケーションのコードです。
import (
"context"
"fmt"
"log"
"net/http"
"github.com/bufbuild/connect-go"
"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
)
必要なパッケージをインポートしています。context
はコンテキスト操作のため、fmt
はフォーマット処理のため、log
はログ出力のためのパッケージです。net/http
はHTTPサーバーを作成するために使用され、http2
とh2c
はHTTP/2サポートを提供します。
github.com/bufbuild/connect-go
パッケージは、Protocol Buffersのメッセージを扱うためのヘルパー関数と機能を提供します。また、example/gen/greet/v1
は、protoc-gen-goによって生成されたGreetServiceのGo言語バインディングを示しています。同様に、example/gen/greet/v1/greetv1connect
は、protoc-gen-connect-goによって生成されたGreetServiceのハンドラを示しています。
type GreetServer struct{}
GreetServer
という構造体型を定義しています。この構造体はGreetServiceの実装を担当します。
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
}
Greet
メソッドがGreetServer
構造体に定義されています。このメソッドはGreetRequest
を受け取り、GreetResponse
を返します。メソッド内では、リクエストヘッダーをログ出力し、GreetResponse
を作成して返します。具体的には、リクエストメッセージのname
フィールドを使用して応答メッセージを生成し、ヘッダー
にカスタムフィールドGreet-Version
を設定します。
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
のインスタンスを作成し、HTTPサーバーを設定します。http.NewServeMux()
で新しいルーターを作成し、greetv1connect.NewGreetServiceHandler(greeter)
を使用してGreetServiceのハンドラを取得し、ルーターに登録します。最後に、http.ListenAndServe()
を使用してサーバーを開始します。h2c.NewHandler()
は、HTTP/2を使用するためにハンドラをラップするために使用されています。
これにより、Protocol Buffersで定義されたGreetServiceの実装が行われたGo言語のサーバーアプリケーションが作成されます。このアプリケーションは、localhost:8080
でHTTP/2プロトコルを使用してリクエストを受け付け、Greetメソッドの処理を行い、応答を返します。
リクエストを作成する
curl \
--header "Content-Type: application/json" \
--data '{"name": "Jane"}' \
http://localhost:8080/greet.v1.GreetService/Greet
{"greeting": "Hello, Jane!"}
クライアントを実装する
package main
import (
"context"
"log"
"net/http"
greetv1 "example/gen/greet/v1"
"example/gen/greet/v1/greetv1connect"
"github.com/bufbuild/connect-go"
)
func main() {
client := greetv1connect.NewGreetServiceClient(
http.DefaultClient,
"http://localhost:8080",
)
res, err := client.Greet(
context.Background(),
connect.NewRequest(&greetv1.GreetRequest{Name: "Jane"}),
)
if err != nil {
log.Println(err)
return
}
log.Println(res.Msg.Greeting)
}
このmain.go
ファイルは、Protocol Buffersのgreet.protoファイルで定義されたGreetServiceのクライアントアプリケーションのコードです。以下でコードの各部分を解説します。
import (
"context"
"log"
"net/http"
greetv1 "example/gen/greet/v1"
"example/gen/greet/v1/greetv1connect"
"github.com/bufbuild/connect-go"
)
必要なパッケージをインポートしています。context
はコンテキスト操作のため、log
はログ出力のため、net/http
はHTTPクライアントを作成するためのパッケージです。
example/gen/greet/v1
パッケージは、protoc-gen-goによって生成されたGreetServiceのGo言語バインディングを示しています。同様に、example/gen/greet/v1/greetv1connect
パッケージは、protoc-gen-connect-goによって生成されたGreetServiceのクライアントを示しています。
func main() {
client := greetv1connect.NewGreetServiceClient(
http.DefaultClient,
"<http://localhost:8080>",
)
res, err := client.Greet(
context.Background(),
connect.NewRequest(&greetv1.GreetRequest{Name: "Jane"}),
)
if err != nil {
log.Println(err)
return
}
log.Println(res.Msg.Greeting)
}
メイン関数が定義されています。ここでは、greetv1connect.NewGreetServiceClient()
を使用してGreetServiceのクライアントを作成し、指定されたエンドポイント("http://localhost:8080")に接続します。
client.Greet()
メソッドを呼び出してGreetリクエストを送信し、レスポンスを受け取ります。リクエストはcontext.Background()
を使用した空のコンテキストと、connect.NewRequest()
を使用して作成されたGreetRequestメッセージを含んでいます(ここでは名前を"Jane"としています)。
エラーチェックを行い、エラーが発生した場合はログに出力します。エラーがない場合は、レスポンスメッセージのGreeting
フィールドをログに出力します。
これにより、Protocol Buffersで定義されたGreetServiceのクライアントが作成され、指定されたエンドポイントにGreetリクエストが送信され、レスポンスが受け取られます。
WithGRPCオプションを使用する
client := greetv1connect.NewGreetServiceClient(
http.DefaultClient,
"http://localhost:8080",
connect.WithGRPC(),
)
**connect.WithGRPC()
**は、gRPCを使用して通信するためのオプションを設定するために使用されます。
通常、Protocol Buffersで生成されたクライアントは、HTTPを介してサーバーと通信しますが、このオプションを指定することで、gRPCを使用して通信するように設定されます。gRPCは、高性能で効率的なRPC(Remote Procedure Call)フレームワークであり、Protocol Buffersと統合されています。
したがって、**WithGRPC()
**オプションを使用することで、クライアントはgRPCを介してサーバーと通信し、gRPCプロトコルを使用してデータをやり取りします。
このように、オプションを使用することで、GreetServiceのクライアントの動作をカスタマイズすることができます。**WithGRPC()
**オプションを指定することで、gRPCを使用して通信する設定が行われます。
Routing
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/bufbuild/connect-go"
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() {
api := http.NewServeMux()
api.Handle(greetv1connect.NewGreetServiceHandler(&GreetServer{}))
mux := http.NewServeMux()
mux.Handle("/grpc/", http.StripPrefix("/grpc", api))
http.ListenAndServe(":http", mux)
}
Discussion