開発環境のセットアップ
以下の環境で動作確認済みです。
- Windows 10 + Go 1.24
必要なGoツールをインストールします。
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/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest
さらにprotocコンパイラが必要です。Protocol Buffersのリリースページからprotoc-<version>-win64.zipをダウンロードし、解凍後にbinディレクトリをシステムPATHに追加してください。
ディレクトリ構成
microservice-demo/
├── proto/
│ └── member/
│ └── member.proto
├── internal/
│ ├── grpcserver/ # gRPCサーバ実装
│ │ └── cmd/
│ │ └── main.go
│ └── httpgw/ # HTTPゲートウェイ
│ └── cmd/
│ └── main.go
├── pkg/
│ └── generated/ # protoc生成コード
│ └── proto/
│ └── member/
├── third_party/ # 外部proto定義
│ └── googleapis/
│ └── google/
│ └── api/
├── go.mod
└── scripts/
└── generate.go # コード生成スクリプト
ステップ1:Protocol Buffersの定義
proto/member/member.protoを作成します。
syntax = "proto3";
package member;
option go_package = "microservice-demo/pkg/generated/proto/member";
import "google/api/annotations.proto";
service MemberManager {
rpc FetchMember (MemberLookup) returns (MemberInfo) {
option (google.api.http) = {
get: "/api/v1/members/{member_id}"
};
}
rpc RegisterMember (MemberRegistration) returns (MemberInfo) {
option (google.api.http) = {
post: "/api/v1/members"
body: "payload"
};
}
}
message MemberLookup {
string member_id = 1;
}
message MemberRegistration {
MemberPayload payload = 1;
}
message MemberPayload {
string full_name = 1;
string mail_address = 2;
}
message MemberInfo {
string member_id = 1;
string full_name = 2;
string mail_address = 3;
int64 registered_at = 4;
}
重要:google/api/annotations.protoはgoogleapisリポジトリから取得し、third_party/googleapisに配置するか、protoc実行時に-Iでインクルードパスを指定してください。
ステップ2:Goコードの自動生成
scripts/generate.goに以下の内容を記述します。
//go:build ignore
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
)
func main() {
protoRoot := "proto"
googleAPI := "third_party/googleapis"
outputDir := "pkg/generated"
// 出力ディレクトリのクリーンアップ
os.RemoveAll(outputDir)
os.MkdirAll(outputDir, 0755)
protoFiles := []string{
"proto/member/member.proto",
}
for _, pf := range protoFiles {
// 標準のGoコードとgRPCコードを生成
cmd := exec.Command("protoc",
"-I="+protoRoot,
"-I="+googleAPI,
"--go_out="+outputDir,
"--go_opt=paths=source_relative",
"--go-grpc_out="+outputDir,
"--go-grpc_opt=paths=source_relative",
"--grpc-gateway_out="+outputDir,
"--grpc-gateway_opt=paths=source_relative",
pf,
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "生成エラー (%s): %v\n", filepath.Base(pf), err)
os.Exit(1)
}
}
fmt.Println("コード生成完了")
}
実行:go run scripts/generate.go
生成されるファイル:
member.pb.go— メッセージ構造体member_grpc.pb.go— gRPCインターフェースmember.pb.gw.go— HTTPゲートウェイハンドラ
ステップ3:gRPCサーバの実装
internal/grpcserver/cmd/main.go
package main
import (
"context"
"log"
"net"
"time"
pb "microservice-demo/pkg/generated/proto/member"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type memberRepository struct {
storage map[string]*pb.MemberInfo
}
func newRepository() *memberRepository {
return &memberRepository{
storage: make(map[string]*pb.MemberInfo),
}
}
type memberHandler struct {
pb.UnimplementedMemberManagerServer
repo *memberRepository
}
func (h *memberHandler) FetchMember(ctx context.Context, req *pb.MemberLookup) (*pb.MemberInfo, error) {
record, exists := h.repo.storage[req.MemberId]
if !exists {
return nil, status.Errorf(codes.NotFound, "member not found: %s", req.MemberId)
}
return record, nil
}
func (h *memberHandler) RegisterMember(ctx context.Context, req *pb.MemberRegistration) (*pb.MemberInfo, error) {
payload := req.Payload
if payload.FullName == "" {
return nil, status.Errorf(codes.InvalidArgument, "full_name is required")
}
newID := generateID()
now := time.Now().Unix()
entity := &pb.MemberInfo{
MemberId: newID,
FullName: payload.FullName,
MailAddress: payload.MailAddress,
RegisteredAt: now,
}
h.repo.storage[newID] = entity
return entity, nil
}
func generateID() string {
// 簡易実装:実際にはUUID等を使用
return fmt.Sprintf("M%d", time.Now().UnixNano())
}
func main() {
port := ":50051"
sock, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("ポートリッスン失敗: %v", err)
}
grpcSrv := grpc.NewServer()
pb.RegisterMemberManagerServer(grpcSrv, &memberHandler{
repo: newRepository(),
})
log.Printf("gRPCサーバ起動: %s", port)
if err := grpcSrv.Serve(sock); err != nil {
log.Fatalf("サーバ実行失敗: %v", err)
}
}
ステップ4:HTTPゲートウェイの実装
internal/httpgw/cmd/main.go
package main
import (
"context"
"log"
"net/http"
"time"
pb "microservice-demo/pkg/generated/proto/member"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// gRPCサーバへの接続
grpcConn, err := grpc.DialContext(ctx,
"localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
)
if err != nil {
log.Fatalf("gRPC接続失敗: %v", err)
}
defer grpcConn.Close()
// ゲートウェイマルチプレクサの構築
mux := runtime.NewServeMux()
// HTTPハンドラの登録
if err := pb.RegisterMemberManagerHandler(ctx, mux, grpcConn); err != nil {
log.Fatalf("ハンドラ登録失敗: %v", err)
}
httpAddr := ":8080"
log.Printf("HTTPゲートウェイ起動: %s", httpAddr)
server := &http.Server{
Addr: httpAddr,
Handler: mux,
}
if err := server.ListenAndServe(); err != nil {
log.Fatalf("HTTPサーバ失敗: %v", err)
}
}
ステップ5:依存関係の管理
go mod init microservice-demo
go get google.golang.org/grpc
go get google.golang.org/protobuf
go get github.com/grpc-ecosystem/grpc-gateway/v2
go mod tidy
ステップ6:動作確認
1. gRPCサーバの起動
go run internal/grpcserver/cmd/main.go
2. HTTPゲートウェイの起動
go run internal/httpgw/cmd/main.go
3. RESTful APIのテスト
メンバー登録:
curl -X POST http://localhost:8080/api/v1/members \
-H "Content-Type: application/json" \
-d '{"full_name":"山田太郎","mail_address":"yamada@example.com"}'
メンバー取得:
curl http://localhost:8080/api/v1/members/[返却されたmember_id]
4. gRPC直接呼び出し(オプション)
grpcurl -plaintext -d '{"member_id":"Mxxxx"}' localhost:50051 member.MemberManager/FetchMember
アーキテクチャの解説
gRPCを第一級の通信プロトコルとする理由
HTTP/2ベースのgRPCは、ストリーム多重化、バイナリシリアライズ、厳格なスキーマによる型安全性を提供します。サービス間通信ではこれらの特性がパフォーマンスと信頼性に直結します。
gRPC-GatewayによるRESTful変換
protoファイル内のgoogle.api.httpアノテーションにより、HTTPメソッド・パスとgRPCメソッドの対応関係を宣言的に定義します。protoc-gen-grpc-gatewayはこれを解析し、HTTPリクエストをgRPC呼び出しに変換するリバースプロキシを生成します。JSON↔Protobufの変換、パスパラメータの抽出、クエリ文字列のマッピングを自動処理します。
設計上の利点
- 単一の真実源:.protoファイルのみをメンテナンスすれば、gRPCクライアントとRESTクライアント双方のインターフェースが同期されます
- 段階的な移行:外部向けにRESTを維持しつつ、内部マイクロサービス間はgRPCで通信することで、既存システムとの互換性を保ちながら近代化が可能です
- ツール連携:OpenAPIドキュメント、各種言語のクライアントSDKを自動生成できます
運用時の考慮点
- 本番環境ではgRPCサーバとゲートウェイを別プロセス・別コンテナとしてデプロイし、Kubernetes等で個別にスケーリング可能に設計してください
- mTLSによる相互認証、認可ミドルウェア、構造化ロギング、分散トレーシングの導入を検討してください
- ゲートウェイ層でタイムアウト、リトライ、サーキットブレーカーを適切に設定し、グレースフルデグラデーションを実現してください