Home > Blog > tech

gRPC และ Protocol Buffers คืออะไร? สอนสร้าง High-Performance API สำหรับ Microservices 2026

grpc protocol buffers guide
gRPC และ Protocol Buffers คืออะไร? สอนสร้าง High-Performance API สำหรับ Microservices 2026
2026-04-10 | tech | 3500 words

ในยุคที่ Microservices กลายเป็นสถาปัตยกรรมมาตรฐานของซอฟต์แวร์สมัยใหม่ การสื่อสารระหว่าง Service ต่างๆ เป็นสิ่งที่สำคัญมาก REST API ที่ใช้ JSON เป็นที่นิยมมานาน แต่เมื่อระบบมีขนาดใหญ่ขึ้น ปัญหาเรื่องประสิทธิภาพก็ตามมา นั่นคือจุดที่ gRPC เข้ามาแก้ปัญหา

gRPC เป็น Framework สำหรับ Remote Procedure Call ที่พัฒนาโดย Google ใช้ HTTP/2 เป็น Transport Layer และ Protocol Buffers เป็นรูปแบบการ Serialize ข้อมูล ทำให้การสื่อสารระหว่าง Service เร็วกว่า REST แบบดั้งเดิมหลายเท่า ในบทความนี้เราจะเรียนรู้ gRPC ตั้งแต่แนวคิดพื้นฐานจนถึงการนำไปใช้งานจริงในระบบ Microservices

gRPC คืออะไร?

gRPC ย่อมาจาก gRPC Remote Procedure Call (ตัว g เปลี่ยนความหมายทุกเวอร์ชัน เช่น Google, Good, Green) เป็น Open Source RPC Framework ที่ Google เปิดตัวในปี 2015 หลังจากใช้ระบบ RPC ภายในชื่อ Stubby มานานกว่า 15 ปี

แนวคิดของ RPC คือการทำให้การเรียกใช้ฟังก์ชันบนเครื่องอื่น (Remote) เหมือนกับการเรียกฟังก์ชันในเครื่องของเราเอง (Local) โดย gRPC จัดการเรื่อง Network Communication, Serialization และ Deserialization ให้ทั้งหมด นักพัฒนาแค่กำหนด Interface ด้วยไฟล์ .proto แล้ว gRPC จะ Generate Code สำหรับทั้ง Client และ Server ให้อัตโนมัติ

คุณสมบัติหลักของ gRPC มีดังนี้:

ทำไมต้อง gRPC? ข้อจำกัดของ REST

REST API ที่ใช้ JSON over HTTP/1.1 มีข้อจำกัดหลายประการเมื่อใช้ในระบบ Microservices ขนาดใหญ่:

ปัญหาของ REST ในระบบ Microservices:

gRPC แก้ปัญหาเหล่านี้ทั้งหมด ด้วย HTTP/2 + Protobuf + Code Generation ทำให้การสื่อสารระหว่าง Service เร็วขึ้น เล็กลง และปลอดภัยจาก Bug มากขึ้น

HTTP/2 — Transport Layer ของ gRPC

gRPC ใช้ HTTP/2 เป็น Transport Protocol ซึ่งมีข้อดีเหนือกว่า HTTP/1.1 อย่างมากมาย HTTP/2 ถูกพัฒนาจาก SPDY ของ Google และกลายเป็นมาตรฐานในปี 2015 คุณสมบัติหลักของ HTTP/2 ที่ gRPC ใช้ประโยชน์ได้แก่:

Multiplexing: HTTP/2 สามารถส่งหลาย Request/Response พร้อมกันผ่าน Connection เดียว ไม่ต้องรอ Response ก่อนถึงจะส่ง Request ถัดไป เรื่อง Head-of-Line Blocking ที่เป็นปัญหาของ HTTP/1.1 จึงหมดไป

Binary Framing: HTTP/2 ส่งข้อมูลเป็น Binary Frame แทน Text ทำให้ Parse ได้เร็วกว่าและใช้ Bandwidth น้อยกว่า

Header Compression (HPACK): HTTP/2 บีบอัด Header ด้วย HPACK Algorithm ลดขนาด Header ที่ส่งซ้ำๆ ได้มาก โดยเฉพาะ Cookie และ Header มาตรฐานที่มีขนาดใหญ่

Server Push: Server สามารถส่งข้อมูลไปยัง Client ก่อนที่ Client จะร้องขอ ซึ่ง gRPC ใช้ความสามารถนี้ในการทำ Server Streaming

Flow Control: HTTP/2 มี Flow Control ในระดับ Stream ทำให้ควบคุมอัตราการส่งข้อมูลได้ละเอียด ป้องกัน Receiver ถูก Overwhelm

Protocol Buffers (Protobuf) — การ Serialize ข้อมูล

Protocol Buffers (หรือ Protobuf) คือ Language-neutral, Platform-neutral Mechanism สำหรับ Serializing Structured Data ที่พัฒนาโดย Google เป็น Binary Format ที่เล็กกว่า JSON 3-10 เท่า และ Serialize/Deserialize ได้เร็วกว่า JSON 20-100 เท่า

ไฟล์ .proto — กำหนด Schema

ทุกอย่างใน gRPC เริ่มต้นที่ไฟล์ .proto ซึ่งเป็น Interface Definition Language (IDL) ที่กำหนดโครงสร้างข้อมูลและ Service ที่ต้องการ

// user.proto
syntax = "proto3";

package user;

option go_package = "github.com/myapp/proto/user";

// Message — กำหนดโครงสร้างข้อมูล
message User {
  int32 id = 1;             // Field number 1
  string name = 2;          // Field number 2
  string email = 3;         // Field number 3
  UserRole role = 4;        // Enum type
  repeated string tags = 5; // Array of strings
  Address address = 6;      // Nested message

  // Oneof — เลือกได้แค่ field เดียว
  oneof contact {
    string phone = 7;
    string line_id = 8;
  }

  // Map type
  map<string, string> metadata = 9;
}

// Enum
enum UserRole {
  USER_ROLE_UNSPECIFIED = 0;
  USER_ROLE_ADMIN = 1;
  USER_ROLE_EDITOR = 2;
  USER_ROLE_VIEWER = 3;
}

// Nested Message
message Address {
  string street = 1;
  string city = 2;
  string province = 3;
  string zip_code = 4;
}

// Timestamp ใช้ Well-Known Types
import "google/protobuf/timestamp.proto";

message UserWithTimestamp {
  int32 id = 1;
  string name = 2;
  google.protobuf.Timestamp created_at = 3;
  google.protobuf.Timestamp updated_at = 4;
}
สำคัญ: Field Number (1, 2, 3, ...) ใน Protobuf ห้ามเปลี่ยนเด็ดขาดหลัง Deploy แล้ว เพราะ Binary Encoding ใช้ Field Number ไม่ใช่ชื่อ Field ถ้าต้องแก้ไขให้เพิ่ม Field ใหม่แทน เพื่อรักษา Backward Compatibility

กฎสำคัญของ Protobuf

// การใช้ reserved เมื่อต้องการลบ field
message User {
  reserved 6, 8;  // field numbers ที่ลบไปแล้ว
  reserved "old_field", "deprecated_field"; // ชื่อ field ที่ลบ

  int32 id = 1;
  string name = 2;
  string email = 3;
}

Code Generation ด้วย protoc

หลังจากเขียนไฟล์ .proto แล้ว ใช้ protoc (Protocol Buffer Compiler) เพื่อ Generate Code สำหรับภาษาที่ต้องการ

# ติดตั้ง protoc
# macOS
brew install protobuf

# Ubuntu
sudo apt install -y protobuf-compiler

# ตรวจสอบเวอร์ชัน
protoc --version

# Generate Go code
protoc --go_out=. --go-grpc_out=. proto/user.proto

# Generate Python code
pip install grpcio grpcio-tools
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. proto/user.proto

# Generate Node.js code (static)
npm install @grpc/proto-loader grpc
npx grpc_tools_node_protoc --js_out=import_style=commonjs,binary:. \
  --grpc_out=grpc_js:. proto/user.proto

# Generate TypeScript
npm install ts-proto
protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto \
  --ts_proto_out=. proto/user.proto

gRPC Service Definition

หลังจากกำหนด Message แล้ว ขั้นตอนต่อไปคือกำหนด Service ซึ่งเป็น Interface ที่กำหนดว่ามี Method อะไรบ้างที่ Client สามารถเรียกใช้ได้

// service.proto
syntax = "proto3";

package userservice;

import "user.proto";

service UserService {
  // 1. Unary RPC — Request เดียว Response เดียว
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
  rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
  rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);

  // 2. Server Streaming — Client ส่ง Request เดียว Server ส่ง Response หลายตัว
  rpc ListUsers(ListUsersRequest) returns (stream User);

  // 3. Client Streaming — Client ส่ง Request หลายตัว Server ตอบ Response เดียว
  rpc UploadUsers(stream User) returns (UploadUsersResponse);

  // 4. Bidirectional Streaming — ทั้ง Client และ Server ส่ง Stream ได้
  rpc SyncUsers(stream UserEvent) returns (stream UserEvent);
}

message GetUserRequest {
  int32 id = 1;
}

message GetUserResponse {
  User user = 1;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
  UserRole role = 3;
}

message CreateUserResponse {
  User user = 1;
}

message ListUsersRequest {
  int32 page_size = 1;
  string page_token = 2;
  string filter = 3;
}

message UploadUsersResponse {
  int32 count = 1;
}

message UserEvent {
  string event_type = 1;
  User user = 2;
}

4 ประเภทของ gRPC Service

gRPC รองรับ 4 รูปแบบการสื่อสาร แต่ละแบบเหมาะกับ Use Case ที่แตกต่างกัน:

1. Unary RPC

เป็นรูปแบบที่ง่ายที่สุด เหมือน REST ปกติ — Client ส่ง Request หนึ่งตัว Server ตอบ Response หนึ่งตัว เหมาะกับการดึงข้อมูล, สร้างข้อมูล, อัปเดตข้อมูลทั่วไป

2. Server Streaming RPC

Client ส่ง Request หนึ่งตัว Server ส่ง Response กลับมาเป็น Stream (หลายตัวต่อเนื่อง) เหมาะกับการดึงข้อมูลจำนวนมาก เช่น ดึงรายการสินค้า 10,000 รายการ, Real-time Feed, การส่ง Log หรือ Events จาก Server

3. Client Streaming RPC

Client ส่ง Request หลายตัวเป็น Stream Server ตอบ Response หนึ่งตัวหลังจากรับข้อมูลครบ เหมาะกับการ Upload ไฟล์ขนาดใหญ่, Batch Processing, การส่งข้อมูล Sensor หลายตัว

4. Bidirectional Streaming RPC

ทั้ง Client และ Server ส่ง Stream ได้พร้อมกัน แต่ละฝั่งอ่านและเขียนอิสระจากกัน เหมาะกับ Chat Application, Real-time Collaboration, Game State Synchronization, Interactive Data Processing

Implementing gRPC Server (Go)

ตัวอย่างการสร้าง gRPC Server ด้วยภาษา Go ซึ่งเป็นภาษาที่นิยมใช้กับ gRPC มากที่สุดเพราะ Go เป็น First-class Citizen ของ gRPC

// server/main.go
package main

import (
    "context"
    "fmt"
    "log"
    "net"
    "sync"

    pb "github.com/myapp/proto/user"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

type userServer struct {
    pb.UnimplementedUserServiceServer
    mu    sync.RWMutex
    users map[int32]*pb.User
    nextID int32
}

func NewUserServer() *userServer {
    return &userServer{
        users:  make(map[int32]*pb.User),
        nextID: 1,
    }
}

// Unary RPC
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    user, ok := s.users[req.Id]
    if !ok {
        return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
    }
    return &pb.GetUserResponse{User: user}, nil
}

// Unary RPC
func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
    s.mu.Lock()
    defer s.mu.Unlock()

    user := &pb.User{
        Id:    s.nextID,
        Name:  req.Name,
        Email: req.Email,
        Role:  req.Role,
    }
    s.users[s.nextID] = user
    s.nextID++

    return &pb.CreateUserResponse{User: user}, nil
}

// Server Streaming RPC
func (s *userServer) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
    s.mu.RLock()
    defer s.mu.RUnlock()

    for _, user := range s.users {
        if err := stream.Send(user); err != nil {
            return err
        }
    }
    return nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    grpcServer := grpc.NewServer()
    pb.RegisterUserServiceServer(grpcServer, NewUserServer())

    fmt.Println("gRPC server listening on :50051")
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

Implementing gRPC Server (Python)

# server.py
import grpc
from concurrent import futures
import user_pb2
import user_pb2_grpc

class UserService(user_pb2_grpc.UserServiceServicer):
    def __init__(self):
        self.users = {}
        self.next_id = 1

    def GetUser(self, request, context):
        user = self.users.get(request.id)
        if not user:
            context.set_code(grpc.StatusCode.NOT_FOUND)
            context.set_details(f"User {request.id} not found")
            return user_pb2.GetUserResponse()
        return user_pb2.GetUserResponse(user=user)

    def CreateUser(self, request, context):
        user = user_pb2.User(
            id=self.next_id,
            name=request.name,
            email=request.email,
            role=request.role,
        )
        self.users[self.next_id] = user
        self.next_id += 1
        return user_pb2.CreateUserResponse(user=user)

    def ListUsers(self, request, context):
        """Server Streaming: ส่ง User ทีละตัว"""
        for user in self.users.values():
            yield user

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    user_pb2_grpc.add_UserServiceServicer_to_server(
        UserService(), server
    )
    server.add_insecure_port("[::]:50051")
    server.start()
    print("gRPC server listening on :50051")
    server.wait_for_termination()

if __name__ == "__main__":
    serve()

Implementing gRPC Server (Node.js)

// server.js
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

const packageDef = protoLoader.loadSync("proto/user.proto", {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const proto = grpc.loadPackageDefinition(packageDef).userservice;

const users = new Map();
let nextId = 1;

const server = new grpc.Server();

server.addService(proto.UserService.service, {
  GetUser: (call, callback) => {
    const user = users.get(call.request.id);
    if (!user) {
      return callback({
        code: grpc.status.NOT_FOUND,
        details: `User ${call.request.id} not found`,
      });
    }
    callback(null, { user });
  },

  CreateUser: (call, callback) => {
    const user = {
      id: nextId++,
      name: call.request.name,
      email: call.request.email,
      role: call.request.role,
    };
    users.set(user.id, user);
    callback(null, { user });
  },

  ListUsers: (call) => {
    // Server Streaming
    for (const user of users.values()) {
      call.write(user);
    }
    call.end();
  },
});

server.bindAsync(
  "0.0.0.0:50051",
  grpc.ServerCredentials.createInsecure(),
  () => {
    console.log("gRPC server on :50051");
  }
);

gRPC Client

หลังจากมี Server แล้ว การสร้าง Client ก็ง่ายมาก เพราะ gRPC Generate Code ให้เราอัตโนมัติจาก .proto file

# Python Client
import grpc
import user_pb2
import user_pb2_grpc

# สร้าง Channel
channel = grpc.insecure_channel("localhost:50051")
stub = user_pb2_grpc.UserServiceStub(channel)

# Unary RPC — สร้าง User
response = stub.CreateUser(user_pb2.CreateUserRequest(
    name="สมชาย",
    email="somchai@example.com",
    role=user_pb2.USER_ROLE_ADMIN,
))
print(f"Created user: {response.user.id}")

# Unary RPC — ดึง User
response = stub.GetUser(user_pb2.GetUserRequest(id=1))
print(f"Got user: {response.user.name}")

# Server Streaming — List Users
for user in stub.ListUsers(user_pb2.ListUsersRequest(page_size=10)):
    print(f"User: {user.name} ({user.email})")

# Client Streaming
def generate_users():
    for i in range(100):
        yield user_pb2.User(
            name=f"User {i}",
            email=f"user{i}@example.com",
        )

response = stub.UploadUsers(generate_users())
print(f"Uploaded {response.count} users")

# Bidirectional Streaming
def event_stream():
    for i in range(10):
        yield user_pb2.UserEvent(
            event_type="sync",
            user=user_pb2.User(name=f"User {i}"),
        )

for event in stub.SyncUsers(event_stream()):
    print(f"Received event: {event.event_type}")

Error Handling — Status Codes

gRPC มีระบบ Error Handling ที่เป็นมาตรฐาน โดยใช้ Status Code ที่กำหนดไว้ชัดเจน ต่างจาก REST ที่ใช้ HTTP Status Code ซึ่งบางครั้งไม่ตรงกับสถานการณ์จริง

gRPC CodeHTTP เทียบเท่าความหมาย
OK (0)200สำเร็จ
CANCELLED (1)499Client ยกเลิก
UNKNOWN (2)500ไม่ทราบสาเหตุ
INVALID_ARGUMENT (3)400Parameter ไม่ถูกต้อง
DEADLINE_EXCEEDED (4)504หมดเวลา
NOT_FOUND (5)404ไม่พบข้อมูล
ALREADY_EXISTS (6)409ข้อมูลซ้ำ
PERMISSION_DENIED (7)403ไม่มีสิทธิ์
RESOURCE_EXHAUSTED (8)429ทรัพยากรหมด (Rate Limit)
UNIMPLEMENTED (12)501ยังไม่ได้ Implement
INTERNAL (13)500Server Error
UNAVAILABLE (14)503Service ไม่พร้อม
UNAUTHENTICATED (16)401ยังไม่ได้ Login
# Python — Return Error with Details
from google.rpc import error_details_pb2
from grpc_status import rpc_status

def GetUser(self, request, context):
    if request.id <= 0:
        context.abort(
            grpc.StatusCode.INVALID_ARGUMENT,
            "User ID must be positive"
        )

    user = self.users.get(request.id)
    if not user:
        context.abort(
            grpc.StatusCode.NOT_FOUND,
            f"User {request.id} not found"
        )
    return user_pb2.GetUserResponse(user=user)

Metadata และ Interceptors

Metadata ใน gRPC เทียบเท่ากับ HTTP Headers ใช้ส่งข้อมูลเพิ่มเติมเช่น Authentication Token, Request ID, Tracing Information

# Python — ส่ง Metadata จาก Client
metadata = [
    ("authorization", "Bearer eyJhbGciOiJIUzI1NiIs..."),
    ("x-request-id", "req-12345"),
    ("x-client-version", "1.0.0"),
]

response = stub.GetUser(
    user_pb2.GetUserRequest(id=1),
    metadata=metadata,
)

# Python — อ่าน Metadata ใน Server
def GetUser(self, request, context):
    metadata = dict(context.invocation_metadata())
    token = metadata.get("authorization", "")
    request_id = metadata.get("x-request-id", "unknown")
    print(f"Request {request_id} with token: {token[:20]}...")
    # ... process request

Interceptors — Middleware สำหรับ gRPC

Interceptors เป็น Middleware ที่ทำงานก่อนและหลัง RPC Call ใช้สำหรับ Logging, Authentication, Metrics, Rate Limiting ได้โดยไม่ต้องแก้ Business Logic

# Python Server Interceptor
class AuthInterceptor(grpc.ServerInterceptor):
    def intercept_service(self, continuation, handler_call_details):
        metadata = dict(handler_call_details.invocation_metadata)
        token = metadata.get("authorization", "")

        if not token.startswith("Bearer "):
            return grpc.unary_unary_rpc_method_handler(
                lambda req, ctx: ctx.abort(
                    grpc.StatusCode.UNAUTHENTICATED,
                    "Missing or invalid token"
                )
            )

        # Validate JWT token here
        return continuation(handler_call_details)

class LoggingInterceptor(grpc.ServerInterceptor):
    def intercept_service(self, continuation, handler_call_details):
        method = handler_call_details.method
        print(f"[gRPC] {method} called")
        return continuation(handler_call_details)

# ใช้ Interceptor
server = grpc.server(
    futures.ThreadPoolExecutor(max_workers=10),
    interceptors=[AuthInterceptor(), LoggingInterceptor()],
)

Authentication — TLS และ JWT

ในระบบ Production gRPC ต้องมี Authentication ที่เหมาะสม gRPC รองรับหลายวิธี ได้แก่ TLS/mTLS สำหรับ Transport Security และ Token-based (JWT) สำหรับ Application-level Authentication

# Python — gRPC with TLS
# Server
server_credentials = grpc.ssl_server_credentials(
    [(private_key, certificate_chain)],
    root_certificates=ca_cert,
    require_client_auth=True,  # mTLS
)
server.add_secure_port("[::]:50051", server_credentials)

# Client
channel_credentials = grpc.ssl_channel_credentials(
    root_certificates=ca_cert,
    private_key=client_key,
    certificate_chain=client_cert,
)
channel = grpc.secure_channel("server:50051", channel_credentials)

# JWT Token — ใช้ Call Credentials
class JWTPlugin(grpc.AuthMetadataPlugin):
    def __init__(self, token):
        self.token = token

    def __call__(self, context, callback):
        callback([("authorization", f"Bearer {self.token}")], None)

call_creds = grpc.metadata_call_credentials(JWTPlugin(jwt_token))
composite_creds = grpc.composite_channel_credentials(
    channel_credentials, call_creds
)
channel = grpc.secure_channel("server:50051", composite_creds)

gRPC-Web สำหรับ Browser

Browser ไม่สามารถเรียก gRPC โดยตรงได้ เพราะ Browser ไม่รองรับ HTTP/2 Trailers ที่ gRPC ต้องใช้ gRPC-Web จึงถูกสร้างขึ้นเป็น Bridge ระหว่าง Browser กับ gRPC Backend

วิธีใช้ gRPC-Web มี 2 แบบหลัก คือใช้ Envoy Proxy เป็น Reverse Proxy ที่แปลง gRPC-Web เป็น gRPC ปกติ หรือใช้ grpc-web npm package ร่วมกับ gRPC Server ที่รองรับ gRPC-Web โดยตรง

# Envoy Proxy Config สำหรับ gRPC-Web
# envoy.yaml
static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 8080 }
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters...
          http_filters:
          - name: envoy.filters.http.grpc_web
          - name: envoy.filters.http.cors
          - name: envoy.filters.http.router
          route_config:
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route: { cluster: grpc_service }
              cors:
                allow_origin_string_match:
                - prefix: "*"
                allow_methods: GET, PUT, DELETE, POST, OPTIONS
                allow_headers: "*"
                expose_headers: grpc-status, grpc-message
  clusters:
  - name: grpc_service
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    typed_extension_protocol_options:
      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
        explicit_http_config:
          http2_protocol_options: {}
    load_assignment:
      cluster_name: grpc_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: grpc-server, port_value: 50051 }

gRPC vs REST — เปรียบเทียบละเอียด

คุณสมบัติgRPCREST
ProtocolHTTP/2HTTP/1.1 (ส่วนใหญ่)
Data FormatProtocol Buffers (Binary)JSON (Text)
Type SafetyStrong (จาก .proto)Weak (ไม่บังคับ)
Code Generationอัตโนมัติ 12+ ภาษาต้องทำเอง (OpenAPI)
Streamingรองรับ 4 แบบต้องใช้ WebSocket/SSE
Performanceเร็วกว่า 2-10xช้ากว่า
Payload Sizeเล็กกว่า 3-10xใหญ่กว่า
Browser Supportต้องใช้ gRPC-Web + Proxyรองรับโดยตรง
Human Readableไม่ (Binary)ใช่ (JSON)
Toolinggrpcurl, BloomRPCcurl, Postman
Cachingไม่มีในตัวHTTP Caching มาตรฐาน
Learning Curveสูงกว่าต่ำกว่า

gRPC vs GraphQL

GraphQL เป็นอีกทางเลือกหนึ่งนอกจาก REST แต่มีจุดประสงค์ต่างจาก gRPC โดย GraphQL เน้นการแก้ปัญหา Over-fetching และ Under-fetching ของ REST สำหรับ Frontend ขณะที่ gRPC เน้นประสิทธิภาพการสื่อสารระหว่าง Backend Services

คุณสมบัติgRPCGraphQL
เหมาะกับService-to-ServiceClient-to-Server (Frontend)
FlexibilityFixed SchemaFlexible Query
Performanceสูงมากปานกลาง
Real-timeStreamingSubscriptions
EcosystemBackend-focusedFull-stack
แนะนำ: ใช้ gRPC สำหรับ Internal Service Communication (Microservices) และ GraphQL หรือ REST สำหรับ External API ที่ให้ Frontend หรือ Third-party ใช้งาน

Load Balancing gRPC

การทำ Load Balancing กับ gRPC ซับซ้อนกว่า REST เพราะ gRPC ใช้ HTTP/2 ซึ่งเป็น Long-lived Connection ทำให้ L4 Load Balancer (TCP level) ไม่สามารถกระจายโหลดได้ดี เนื่องจาก Connection ถูกสร้างครั้งเดียวแล้วใช้ต่อไปเรื่อยๆ ต้องใช้ L7 Load Balancer ที่เข้าใจ HTTP/2 เช่น Envoy, Nginx (1.13.10+), HAProxy (2.0+), Linkerd, Istio

นอกจากนี้ gRPC ยังรองรับ Client-side Load Balancing ผ่าน Name Resolution และ Load Balancing Policy ในตัว ทำให้ Client สามารถกระจายโหลดไปยัง Backend หลายตัวได้โดยไม่ต้องมี Proxy ตรงกลาง วิธีนี้ลด Latency เพราะไม่มี Hop เพิ่มเติม แต่ต้องจัดการ Service Discovery เอง

gRPC ใน Kubernetes

Kubernetes เป็น Platform ที่นิยมใช้ gRPC มากที่สุด แต่มีปัญหาเรื่อง Load Balancing ที่ต้องระวัง Kubernetes Service (ClusterIP) ทำ L4 Load Balancing ซึ่งกระจาย Connection ไม่ใช่ Request ทำให้ Pod ตัวหนึ่งอาจรับ Request มากกว่าตัวอื่นเพราะ Connection ถูกสร้างแค่ครั้งเดียว วิธีแก้ปัญหามีหลายทาง ดังนี้:

ใช้ Headless Service + Client-side LB: ใช้ Headless Service ของ Kubernetes (clusterIP: None) ที่คืน IP ของ Pod ทุกตัว แล้วให้ gRPC Client ทำ Round-robin เอง

ใช้ Service Mesh (Istio/Linkerd): Service Mesh จะ Inject Sidecar Proxy ที่ทำ L7 Load Balancing ให้อัตโนมัติ เป็นวิธีที่ง่ายที่สุดและนิยมใน Production

ใช้ Ingress Controller ที่รองรับ gRPC: เช่น Nginx Ingress, Traefik, Ambassador ที่ทำ L7 Load Balancing สำหรับ gRPC Traffic

Health Checking และ Reflection

gRPC มีมาตรฐาน Health Checking Protocol ที่ใช้ตรวจสอบว่า Service ยังทำงานอยู่หรือไม่ ซึ่งสำคัญมากใน Kubernetes และ Load Balancer

// health.proto (มาตรฐานจาก gRPC)
syntax = "proto3";

package grpc.health.v1;

service Health {
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
  rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

message HealthCheckRequest {
  string service = 1;
}

message HealthCheckResponse {
  enum ServingStatus {
    UNKNOWN = 0;
    SERVING = 1;
    NOT_SERVING = 2;
    SERVICE_UNKNOWN = 3;
  }
  ServingStatus status = 1;
}

Reflection ช่วยให้ Client สามารถค้นหา Service และ Method ที่มีอยู่บน Server ได้โดยไม่ต้องมีไฟล์ .proto ล่วงหน้า เหมือนกับ Swagger ของ REST ช่วยให้เครื่องมืออย่าง grpcurl สามารถเรียก Service ได้ทันทีโดยไม่ต้อง Compile proto file

# Python — เปิด Reflection
from grpc_reflection.v1alpha import reflection

SERVICE_NAMES = (
    user_pb2.DESCRIPTOR.services_by_name['UserService'].full_name,
    reflection.SERVICE_NAME,
)
reflection.enable_server_reflection(SERVICE_NAMES, server)

Testing gRPC

การทดสอบ gRPC Service มีเครื่องมือหลายตัว ทั้ง CLI และ GUI ที่ช่วยให้ทดสอบได้ง่ายเหมือน Postman สำหรับ REST

grpcurl — Command Line

# ติดตั้ง
brew install grpcurl  # macOS
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest  # Go

# List services (ต้องเปิด Reflection)
grpcurl -plaintext localhost:50051 list

# Describe service
grpcurl -plaintext localhost:50051 describe userservice.UserService

# เรียก Unary RPC
grpcurl -plaintext -d '{"id": 1}' localhost:50051 userservice.UserService/GetUser

# เรียกพร้อม Metadata
grpcurl -plaintext \
  -H "authorization: Bearer eyJ..." \
  -d '{"name": "test", "email": "test@test.com"}' \
  localhost:50051 userservice.UserService/CreateUser

BloomRPC / Evans — GUI Tools

BloomRPC เป็น GUI Client สำหรับ gRPC เหมือน Postman ของ REST ให้ Load ไฟล์ .proto แล้วสามารถเรียก RPC ได้ทันที มี UI สวยงาม ใช้งานง่าย เหมาะกับ Developer ที่ไม่คุ้นเคยกับ CLI ส่วน Evans เป็นอีกเครื่องมือที่เป็น Interactive CLI Client ที่รองรับ Reflection ได้ดี

Unit Testing gRPC

# Python — Testing gRPC with pytest
import grpc
import grpc_testing
import user_pb2
import user_pb2_grpc

class TestUserService:
    def setup_method(self):
        # สร้าง In-process gRPC server สำหรับ test
        self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=2))
        self.service = UserService()
        user_pb2_grpc.add_UserServiceServicer_to_server(self.service, self.server)
        port = self.server.add_insecure_port("[::]:0")
        self.server.start()

        channel = grpc.insecure_channel(f"localhost:{port}")
        self.stub = user_pb2_grpc.UserServiceStub(channel)

    def teardown_method(self):
        self.server.stop(0)

    def test_create_user(self):
        response = self.stub.CreateUser(user_pb2.CreateUserRequest(
            name="Test User",
            email="test@test.com",
            role=user_pb2.USER_ROLE_ADMIN,
        ))
        assert response.user.id == 1
        assert response.user.name == "Test User"

    def test_get_user_not_found(self):
        with pytest.raises(grpc.RpcError) as exc_info:
            self.stub.GetUser(user_pb2.GetUserRequest(id=999))
        assert exc_info.value.code() == grpc.StatusCode.NOT_FOUND

เมื่อไหร่ควรใช้ gRPC?

ใช้ gRPC เมื่อ:

ไม่ควรใช้ gRPC เมื่อ:

Migration จาก REST ไปเป็น gRPC

การย้ายจาก REST ไป gRPC ไม่จำเป็นต้องทำทีเดียวทั้งหมด สามารถทำแบบค่อยเป็นค่อยไปได้ ขั้นตอนแนะนำมีดังนี้:

  1. เริ่มจาก Internal Services ก่อน — เลือก Service ที่สื่อสารกันภายในมากที่สุดและเป็น Bottleneck ด้านประสิทธิภาพ
  2. สร้าง .proto file จาก REST API เดิม — แปลง JSON Schema เป็น Protobuf Message
  3. รัน gRPC + REST คู่กัน — ใช้ gRPC-Gateway ที่แปลง gRPC เป็น REST API อัตโนมัติ ทำให้ Client เดิมยังใช้ REST ได้
  4. ย้าย Client ทีละตัว — เริ่มย้าย Client ไปใช้ gRPC Client ที่ Generate จาก .proto file
  5. ปิด REST Endpoint เมื่อย้ายครบ — เมื่อ Client ทุกตัวย้ายไป gRPC แล้วก็ปิด REST ได้
// ใช้ gRPC-Gateway เพื่อรัน REST + gRPC คู่กัน
// service.proto — เพิ่ม HTTP annotations
import "google/api/annotations.proto";

service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse) {
    option (google.api.http) = {
      get: "/api/v1/users/{id}"
    };
  }
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {
    option (google.api.http) = {
      post: "/api/v1/users"
      body: "*"
    };
  }
}
เคล็ดลับ: gRPC-Gateway เป็นวิธีที่ดีที่สุดในการ Migration เพราะ .proto file ตัวเดียวสามารถ Generate ได้ทั้ง gRPC Server Code และ REST Reverse Proxy ทำให้ไม่ต้องเขียน REST API แยก

Best Practices สำหรับ gRPC

การใช้ gRPC ให้ได้ประสิทธิภาพสูงสุดมีแนวปฏิบัติที่ควรยึดถือ ดังนี้:

สรุป

gRPC และ Protocol Buffers เป็นเทคโนโลยีที่เปลี่ยนวิธีการสื่อสารระหว่าง Microservices อย่างมาก ด้วยประสิทธิภาพที่เหนือกว่า REST หลายเท่า Type Safety จาก .proto file และ Code Generation อัตโนมัติ ทำให้การพัฒนาระบบ Microservices ง่ายขึ้นและเร็วขึ้น

ในปี 2026 gRPC กลายเป็นมาตรฐานสำหรับ Internal Communication ใน Microservices โดยเฉพาะในระบบที่ใช้ Kubernetes และ Service Mesh บริษัทระดับโลกอย่าง Google, Netflix, Square, Dropbox ล้วนใช้ gRPC เป็นหลัก

เริ่มต้นเรียนรู้ gRPC วันนี้ เริ่มจากเขียน .proto file ง่ายๆ Generate Code แล้วลองสร้าง Server และ Client ดู เมื่อเข้าใจแนวคิดแล้ว คุณจะเห็นว่า gRPC ไม่ได้ยากอย่างที่คิด และมันจะเปลี่ยนวิธีคิดเกี่ยวกับ API Design ของคุณไปตลอด


Back to Blog | iCafe Forex | SiamLanCard | Siam2R