GoaのDSLでgRPCサービスを設計した後は、それを実際に動作させる時です!このガイドでは、サービスの実装を段階的に説明します。以下の方法を学びます:
まず、必要なgRPCコードをすべて生成しましょう。プロジェクトのルート(例:grpcgreeter/
)から以下を実行します:
goa gen grpcgreeter/design
go mod tidy
このコマンドはgRPCの設計(greeter.go
)を分析し、gen/
ディレクトリに必要なコードを生成します。以下のものが作成されます:
gen/
├── grpc/
│ └── greeter/
│ ├── pb/ # Protocol Buffersの定義
│ ├── server/ # サーバーサイドのgRPCコード
│ └── client/ # クライアントサイドのgRPCコード
└── greeter/ # サービスインターフェースと型
goa gen
を再実行して、生成されたコードをサービス定義と同期させてください。Goaが生成したものを見ていきましょう:
greeter.proto
: Protocol Buffersのサービス定義service Greeter {
rpc SayHello (SayHelloRequest) returns (SayHelloResponse);
}
greeter.pb.go
: .proto
ファイルからコンパイルされたGoコードserver.go
: サービスメソッドをgRPCハンドラーにマッピングencode_decode.go
: サービスの型とgRPCメッセージ間の変換types.go
: サーバー固有の型定義client.go
: gRPCクライアントの実装encode_decode.go
: クライアントサイドのシリアライゼーションロジックtypes.go
: クライアント固有の型定義いよいよ楽しい部分 - サービスロジックの実装です!サービスパッケージにgreeter.go
という新しいファイルを作成します:
package greeter
import (
"context"
"fmt"
// 生成されたパッケージに説明的なエイリアスを使用
gengreeter "grpcgreeter/gen/greeter"
)
// GreeterServiceはServiceインターフェースを実装します
type GreeterService struct{}
// NewGreeterServiceは新しいサービスインスタンスを作成します
func NewGreeterService() *GreeterService {
return &GreeterService{}
}
// SayHelloは挨拶のロジックを実装します
func (s *GreeterService) SayHello(ctx context.Context, p *gengreeter.SayHelloPayload) (*gengreeter.SayHelloResult, error) {
// 必要に応じて入力バリデーションを追加
if p.Name == "" {
return nil, fmt.Errorf("名前を空にすることはできません")
}
// 挨拶を構築
greeting := fmt.Sprintf("こんにちは、%sさん!", p.Name)
// 結果を返す
return &gengreeter.SayHelloResult{
Greeting: greeting,
}, nil
}
cmd/greeter/main.go
にサーバーのエントリーポイントを作成します:
package main
import (
"context"
"log"
"net"
"os"
"os/signal"
"syscall"
"grpcgreeter"
gengreeter "grpcgreeter/gen/greeter"
genpb "grpcgreeter/gen/grpc/greeter/pb"
genserver "grpcgreeter/gen/grpc/greeter/server"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
func main() {
// TCPリスナーを作成
lis, err := net.Listen("tcp", ":8090")
if err != nil {
log.Fatalf("リッスンに失敗しました: %v", err)
}
// オプション付きで新しいgRPCサーバーを作成
srv := grpc.NewServer(
grpc.UnaryInterceptor(loggingInterceptor),
)
// サービスを初期化
svc := greeter.NewGreeterService()
// エンドポイントを作成
endpoints := gengreeter.NewEndpoints(svc)
// gRPCサーバーにサービスを登録
genpb.RegisterGreeterServer(srv, genserver.New(endpoints, nil))
// デバッグツール用にサーバーリフレクションを有効化
reflection.Register(srv)
// グレースフルシャットダウンを処理
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
log.Println("gRPCサーバーをシャットダウンしています...")
srv.GracefulStop()
}()
// サービスを開始
log.Printf("gRPCサーバーが:8090でリッスンしています")
if err := srv.Serve(lis); err != nil {
log.Fatalf("サービスの提供に失敗しました: %v", err)
}
}
// ロギングインターセプターの例
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf("%sを処理中", info.FullMethod)
return handler(ctx, req)
}
gRPCサーバーの主要なコンポーネントを見ていきましょう:
TCPリスナーのセットアップ:
lis, err := net.Listen("tcp", ":8090")
gRPC接続用にポート8090を開きます。ここでサービスがクライアントリクエストをリッスンします。
サーバーの作成:
srv := grpc.NewServer(
grpc.UnaryInterceptor(loggingInterceptor),
)
ミドルウェア(インターセプター)サポート付きの新しいgRPCサーバーを作成します。ロギングインターセプターは全ての受信リクエストをログに記録します。
サービスの登録:
svc := greeter.NewGreeterService()
endpoints := gengreeter.NewEndpoints(svc)
genpb.RegisterGreeterServer(srv, genserver.New(endpoints, nil))
サーバーリフレクション:
reflection.Register(srv)
gRPCリフレクションを有効化し、grpcurl
のようなツールがサービスメソッドを動的に発見できるようにします。
グレースフルシャットダウン:
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
srv.GracefulStop()
}()
リクエストロギング:
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf("%sを処理中", info.FullMethod)
return handler(ctx, req)
}
grpcurl
のようなツールがサービスを発見可能サービスをビルド:
go build -o greeter cmd/greeter/main.go
サーバーを実行:
./greeter
これでgRPCサービスが実行され、ポート8090で接続を受け付ける準備ができました!
サービスが実装され実行されたので、以下のことができます:
より高度なトピックについては、gRPCの概念セクションをチェックしてください。ストリーミング、ミドルウェア、エラー処理などについて説明しています。