正しいgRPCの実装フロー
https://scrapbox.io/files/68c61d072bea67513f635adf.png
1. .protoファイルの定義
code:proto
// chat.proto - RESTとは全く異なるアプローチ
syntax = "proto3";
package chat;
option go_package = "./chatpb";
// メッセージ定義
message ChatMessage {
string user_id = 1;
string content = 2;
int64 timestamp = 3;
}
message Empty {}
// サービス定義(RESTのリソースではない)
service ChatService {
// 4つの通信方式すべて定義可能
rpc SendMessage(ChatMessage) returns (Empty); // Unary
rpc ReceiveMessages(Empty) returns (stream ChatMessage); // Server Streaming
rpc SendMessages(stream ChatMessage) returns (Empty); // Client Streaming
rpc Chat(stream ChatMessage) returns (stream ChatMessage); // Bidirectional
}
2. コード生成
code:sh
# Protocol Buffersコンパイラでコード生成
protoc --go_out=. --go-grpc_out=. chat.proto
# 生成されるファイル:
# - chat.pb.go (メッセージ構造体)
# - chat_grpc.pb.go (サービスインターフェース)
3. サーバー実装
code:go
// server.go
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "path/to/chatpb"
)
// サーバー構造体
type ChatServer struct {
pb.UnimplementedChatServiceServer
clients mapstringchan *pb.ChatMessage
}
// Unary RPC実装
func (s *ChatServer) SendMessage(ctx context.Context, msg *pb.ChatMessage) (*pb.Empty, error) {
log.Printf("Received: %s from %s", msg.Content, msg.UserId)
// 全クライアントにブロードキャスト
for _, ch := range s.clients {
select {
case ch <- msg:
default:
}
}
return &pb.Empty{}, nil
}
// Server Streaming RPC実装
func (s *ChatServer) ReceiveMessages(req *pb.Empty, stream pb.ChatService_ReceiveMessagesServer) error {
clientID := "client_" + generateID()
msgChan := make(chan *pb.ChatMessage, 100)
s.clientsclientID = msgChan
defer delete(s.clients, clientID)
// 無限ループでメッセージをストリーミング
for {
select {
case msg := <-msgChan:
if err := stream.Send(msg); err != nil {
return err
}
case <-stream.Context().Done():
return nil
}
}
}
// Bidirectional Streaming RPC実装
func (s *ChatServer) Chat(stream pb.ChatService_ChatServer) error {
go func() {
// 受信ループ
for {
msg, err := stream.Recv()
if err != nil {
return
}
log.Printf("Received in chat: %s", msg.Content)
// エコーバック
echoMsg := &pb.ChatMessage{
UserId: "server",
Content: "Echo: " + msg.Content,
}
stream.Send(echoMsg)
}
}()
// 接続維持
<-stream.Context().Done()
return nil
}
func main() {
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
// gRPCサーバー作成(HTTP/2自動使用)
s := grpc.NewServer()
chatServer := &ChatServer{
clients: make(mapstringchan *pb.ChatMessage),
}
pb.RegisterChatServiceServer(s, chatServer)
log.Println("gRPC server starting on :8080")
if err := s.Serve(lis); err != nil {
log.Fatal(err)
}
}
code:RESTとの根本的な違い.sh
# REST(リソース指向)
# OpenAPI - リソースベース
GET /users/123 # ユーザー取得
POST /users # ユーザー作成
PUT /users/123 # ユーザー更新
DELETE /users/123 # ユーザー削除
# gRPC(RPC指向)
# Protocol Buffers - メソッド呼び出しベース
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
rpc UpdateUser(UpdateUserRequest) returns (User);
rpc DeleteUser(DeleteUserRequest) returns (Empty);
}
code:実際のHTTP/2通信の違い.sh
# REST over HTTP/2
CopyPOST /api/messages HTTP/2
Content-Type: application/json
{"user": "john", "message": "hello"}
# gRPC over HTTP/2
gRPC Frame (Binary)
- Method: /chat.ChatService/SendMessage
- Content-Type: application/grpc
- Data: Protocol Buffers Binary