🐥

膨大な文法にパニックなあなたに向けたgRPCサーバー側

に公開

はじめに

このドキュメントは、Go言語の基本文法を一通り習得済みながら、長期記憶に定着しにくい初心者・中級開発者を主な対象としています。gRPCの学習を通じてGoの文法要素を体系的に復習・強化し、分散システム開発の基盤を築きたい方に適しています。本資料作成者自身も、Goの文法習得後、記憶の定着に課題を抱えており、このドキュメントを自身のスキル棚卸(自己診断)と復習ツールとして活用する意図でまとめています。これにより、対象者と作成者の共通課題を共有し、互いの学習プロセスを加速させることを目指します。

gRPCの2層構造

Protocol Buffers = 世界共通語

Protocol Buffers: どの言語でも同じデータとして扱える「世界共通語」
HTTP/2: 一つの接続で複数のやりとりを同時にできる「高速道路」

gRPCの性能の秘密を理解するには、2つの異なる層での最適化を分けて考える必要があります。

技術層 担当領域 特徴 依存性
Protocol Buffers(シリアライズ層) データの表現化 意味・互換性重視 グローバル:言語・プラットフォーム非依存
HTTP/2多重化(トランスポート層) 通信の効率化 性能・規格重視 ローカル:ネットワーク・実装依存

シリアライズ・デシリアライズとは

シリアライズ: メモリ上のデータ → バイト列(送信可能な形)
デシリアライズ: 受信したバイト列 → メモリ上のデータ

Protocol Buffers = 世界共通語

実際の変換例
Go言語:   User{Name: "田中", Age: 25}
   ↓ シリアライズ(翻訳)
バイナリ:  [謎のバイト列]
   ↓ デシリアライズ(翻訳)
C#:      new User{Name = "田中", Age = 25}

Protocol Buffersの主な利点

側面 利点
言語非依存 複数言語で同一スキーマ使用可能。システム柔軟性向上
更新時 後方互換性が高く、フィールド追加/削除が容易。ダウンタイム最小限。
サイズ バイナリ形式でJSONより倍小さく、転送/ストレージ効率的。高速処理。

HTTP/2 = 高速道路

HTTP/1.1の問題(渋滞)
リクエスト1 [████████] → レスポンス1
リクエスト2 [████████] → レスポンス2
リクエスト3 [████████] → レスポンス3
↑ 一個ずつしか処理できない

HTTP/2の解決(並行処理)
リクエスト1 [██] [██] [██] → レスポンス1
リクエスト2 [██] [██] [██] → レスポンス2
リクエスト3 [██] [██] [██] → レスポンス3
↑ 同時に複数処理できる(多重化・インターリーブ)

REST + JSON の問題点

// 手作業でのシリアライズ・デシリアライズ
const response = await fetch('/api/user', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({name: '田中', age: 25}) // 手動変換
});
const data = await response.json(); // エラーの可能性

gRPC 解決

// 自動生成されたコードを使うだけ
response, err := client.CreateUser(ctx, &pb.User{
    Name: "田中",
    Age: 25,
})
// 型安全、エラー処理も自動

gRPCの概要

gRPCは、Googleが開発した高性能なRPC(Remote Procedure Call)フレームワークで、Protocol BuffersとHTTP/2という2つの独立した技術を基盤に、データの互換性と通信効率の両方を同時に解決します。開発者はシンプルに.protoファイル(サービス定義とメッセージ構造を記述)を作成するだけで、これらの恩恵を自動的に享受できます。

環境構築

git clone [リポジトリ]
Go1.25 install済

go version
go version go1.25.0 windows/amd64

gRPCの特徴

gRPC RESTful API (OpenAPI)
定義ファイル Protocol Buffers (.proto) OpenAPI Specification(openapi.yaml)
データ形式 バイナリ(Protocol Buffers) テキスト(JSON)
プロトコル HTTP/2 HTTP/1.1 or HTTP/2
開発フロー Proto-first サービス間の「契約」を最初に決める。 Code-first サーバー側を実装してから、そのAPI仕様を共有する。
用途 主にマイクロサービス間の通信 Web APIの標準、外部連携など

gRPC Protocol Buffersの定義

//version定義
syntax = "proto3";


//自動生成させるGoのコードをpkg/api直下に配置する指定
option go_package = "gen/api";

// Protocol Buffersのパッケージ名を指定
package helloworld;

//サービスとメソッドの定義
service GreetingService{
    //SayHelloというメソッドをリモートプロシージャコール(RPC)に定義
    rpc SayHello(HelloReuest)returns(HelloResponse){}
}

//メッセージ型の定義
message HelloRequest {
	string name = 1;
}

message HelloResponse {
	string message = 1;
}

自動生成の準備と実行

Protocol Buffers コンパイラ(protoc)
Go言語用のprotocプラグイン(protoc-gen-goとprotoc-gen-go-grpc)
以下の手順でインストールできます。

1. protocのインストール

GitHubからダウンロード:
https://github.com/protocolbuffers/protobuf/releases/
Protocol Buffersのリリースページにアクセスします。
お使いのWindowsのOSとアーキテクチャ(win32またはwin64)に対応したprotoc-xx.x-winxx.zipというファイルをダウンロードします。
ダウンロードしたzipファイルを解凍し、中にあるprotoc.exeを任意のディレクトリ(例: C:\protoc\bin)に配置します。

2. Go言語用プラグインのインストール

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
protoc --version
libprotoc 32.0

3.自動生成コマンドの実行

protoc --go_out=. --go-grpc_out=. proto/helloworld.proto

(毎回長いコマンドを入力するのは面倒なのでMakefileで可能だそうです今回は割愛)

このコマンドの説明:
--go_out=.: Protocol BuffersのメッセージとシリアライゼーションコードをGoで生成
--go-grpc_out=.: gRPCサービスのクライアント/サーバーコードをGoで生成
prpto/helloworld.proto: 入力となる.protoファイル

4.最終的なディレクトリ構成

grpc-app-sample/
├── cmd/
│ └── server/ #サーバーのエントリーポイントを格納するディレクトリ
│ └── main.go # gRPCサーバーのエントリーポイント
├── proto/
│ └── helloworld.proto
├── gen/ # genは「generated(生成された)」の略
│ └── api/
│ ├── helloworld.pb.go # Protocol Buffersメッセージの定義
│ └── helloworld_grpc.pb.go  # gRPCサービスの定義
├── go.mod
└── go.sum

補足

gRPCは、クライアントとサーバー間の通信に特化した技術です。
HTTP/2の多重化という機能により、1つのポートで複数のリクエストを同時に処理できます。
複数のサービスを同じサーバー内で提供するため、ポートを(例: 8080/3030)分ける必要がありません

サーバーを起動する部分のコード

1. パッケージ宣言とインポート文法

package main

2. 型定義(Type Definition)

import (
    "context"
    "fmt"
    "log"
    "net"
    "os"
    "os/signal"

    hellopb "grpc-app-sample/gen/api" //生成されたコードのインポート

    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
)

3. 関数定義とポインタ

func NewMyServer() *myServer {
    return &myServer{}
}

4. メソッド定義とレシーバー

func (s *myServer) Hello(ctx context.Context, req *hellopb.HelloRequest) (*hellopb.HelloResponse, error) {
    return &hellopb.HelloResponse{
        Message: fmt.Sprintf("Hello, %s!", req.GetName()),
    }, nil
}

5. main関数と変数宣言

func main() {
    port := 8080
    listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
    if err != nil {
        panic(err)
    }

エラーハンドリングパターン

if err != nil {
    panic(err)
}

6. 変数宣言と代入

s := grpc.NewServer()

7. 関数呼び出し

hellopb.RegisterGreetingServiceServer(s, NewMyServer())
reflection.Register(s)

gRPCサーバーにGreetingServiceを登録
hellopb.RegisterGreetingServiceServer(s, [サーバーに登録するサービス])

8. Goroutineと無名関数

go func() {
    log.Printf("start gRPC server port: %v", port)
    s.Serve(listener)
}()

9. チャンネル(Channel)とシグナル(Signal)

quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit

10. ログ出力とメソッド呼び出し

log.Println("stopping gRPC server...")
s.GracefulStop()

サーバー側の実装まとめ

package main

//パッケージ宣言とインポート文法
import (
	"context"
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"

	hellopb "grpc-app-sample/gen/api" //生成されたコードのインポート

	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
)

// 型定義
// hellopb.UnimplementedGreetingServiceServer の全メソッドが myServer で使用可能
// 継承ではなく「委譲」の仕組み
type myServer struct {
	hellopb.UnimplementedGreetingServiceServer
}

// 関数定義とポインタレシー
func NewMyServer() *myServer {
	return &myServer{}
}

// メソッド定義とレシーバー
func (s *myServer) Hello(ctx context.Context, req *hellopb.HelloRequest) (*hellopb.HelloResponse, error) {
	// return
	return &hellopb.HelloResponse{
		Message: fmt.Sprintf("Hello, %s!", req.GetName()),
	}, nil
}

// main関数と変数宣言
func main() {
	// 8080番ポートのListenerを作成
	port := 8080
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	//エラーハンドリングパターン
	if err != nil {
		panic(err)
	}
	//変数宣言と代入
	s := grpc.NewServer()
	//関数呼び出し
	hellopb.RegisterGreetingServiceServer(s, NewMyServer())

	//この行がリフレクション機能を有効にしている。おかげでgrpcurlが使える!
	reflection.Register(s)

	go func() {
		log.Printf("start gRPC server port: %v", port)
		s.Serve(listener)
	}()
	//チャンネル(Channel)とシグナル(Signal)
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt)
	<-quit
	//ログ出力とメソッド呼び出し
	log.Println("stopping gRPC server...")
	s.GracefulStop()
}

grpcurlでこのコードの動作確認

Go経由でインストール(最も確実)
Goは既にインストールされているので、これが一番簡単です。

powershellgo install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
powershellgrpcurl --version
go run main.go

2025/09/22 00:33:28 start gRPC server port: 8080

新しいPowerShellウィンドウを開いてgrpcurlでテスト

grpcurl -plaintext localhost:8080 list

期待される出力
grpc.reflection.v1.ServerReflection - リフレクション機能
grpc.reflection.v1alpha.ServerReflection - 古いバージョンのリフレクション
helloworld.GreetingService - あなたが実装したサービス

次回、client側の連携の理解を試みたいと思います。

参考

https://qiita.com/4_mio_11/items/08e972ae361c55b317f0
https://zenn.dev/hsaki/books/golang-grpc-starting
https://wa3.i-3-i.info/word111503.html
https://wa3.i-3-i.info/word111504.html

Discussion