Skip to content
← Go · মধ্যবর্তী · 20 মিনিট · 16 / 25 EN

gRPC ও Protocol Buffers

High-performance service-to-service communication — service-রা যখন একে অপরের সাথে কথা বলে, gRPC তখন REST যা হতে চায় তা-ই।

gRPCprotobufRPCmicroservicesstreamingservice communication

গল্পে বুঝি

ইবনে সিনার কোম্পানির দুটো অফিস — ঢাকায় হেড অফিস, চট্টগ্রামে ব্রাঞ্চ। আগে দুই অফিস একে অপরকে লম্বা চিঠি লিখত — “অমুক গ্রাহকের নাম, ঠিকানা, অর্ডার নম্বর, তারিখ…” — পুরো কাহিনি প্রতিবার নতুন করে লেখা। কেউ একটা শব্দ এদিক-ওদিক লিখলে অন্য অফিস ভুল বুঝত, আর মোটা খামে ডাকখরচও বেশি পড়ত।

তাই আল-খোয়ারিজমি একটা বুদ্ধি বের করল। সে একজন ছাপাখানার মালিককে দিয়ে দুই অফিসের জন্য একদম হুবহু একই রকম ছাপানো ফর্ম বানাল — যেখানে ঘর নম্বর ১ মানে গ্রাহকের নাম, ঘর ২ মানে অর্ডার নম্বর, ঘর ৩ মানে তারিখ। এখন হেড অফিস আর ব্রাঞ্চ, দুই জায়গাতেই এক তাড়া খালি ফর্ম। চিঠি লেখার দরকার নেই — ফাতিমা আল-ফিহরি শুধু ঘরগুলোতে ছোট ছোট সংখ্যা বসিয়ে একটা এক টুকরো স্লিপ পাঠায়। ওপারে যেহেতু হুবহু একই ফর্ম, ফাতিমা আল-ফিহরির সহকর্মী চোখ বন্ধ করেই বলে দিতে পারে ঘর ২-এ যা আছে সেটাই অর্ডার নম্বর। কোনো ভুল বোঝাবুঝি নেই, স্লিপ ছোট, পাঠানোও দ্রুত।

এই ছাপানো ফর্মটাই হলো gRPC-র .proto contract — client আর server দুই পক্ষ একই schema-তে একমত। ছাপাখানার মালিক, যে দুই পক্ষের জন্য মিলিয়ে খালি ফর্ম বানিয়ে দেয়, সে-ই protoc code generation: এক .proto থেকে দুই দিকের মিলে-যাওয়া কোড তৈরি করে দেয়। আর ঘরে-ভরা সেই ছোট্ট স্লিপ হলো compact binary message — লম্বা JSON চিঠির বদলে কেবল দরকারি মানটুকু, তাই হালকা ও দ্রুত। বাস্তবে এভাবেই এক backend service আরেক service-কে service-to-service ডাকে: দুই পক্ষ আগে থেকেই ফর্ম চেনে বলে ব্যাখ্যার দরকার হয় না, শুধু ভরা-স্লিপ যায়-আসে। Google, Netflix-এর মতো কোম্পানি তাদের ভেতরের হাজারো microservice-এর কথাবার্তা ঠিক এভাবেই gRPC দিয়ে চালায়।

gRPC কেন?

browser-to-server communication-এর জন্য REST দারুণ। কিন্তু আপনার backend-এর ভেতরে service-to-service communication-এর জন্য gRPC প্রায়ই ভালো পছন্দ:

FeatureREST/JSONgRPC/Protobuf
SerializationJSON (text, ~10x বড়)Protobuf (binary, compact)
SchemaOpenAPI (optional, প্রায়ই পুরনো).proto file (required, সবসময় হালনাগাদ)
Code generationOptionalBuilt-in (Go, Java, Python, ইত্যাদি)
StreamingWebSocket (আলাদা protocol)Built-in bidirectional streaming
Performanceভালোদুর্দান্ত (2-10x দ্রুত serialization)
Browser supportNativegRPC-Web proxy দরকার

বাস্তব জীবনের উপমা

REST অনেকটা চিঠি পাঠানোর মতো — প্রতিটা চিঠির একটা ঠিকানা (URL), একটা format (JSON) লাগে, আর ডাকঘর (HTTP) সেটা পৌঁছে দেয়। gRPC অনেকটা ফোন কলের মতো — আপনি একটা connection স্থাপন করেন, দুই পক্ষ একটা shared language (protobuf) বলে, আর communication তাৎক্ষণিক ও bidirectional।

Protobuf দিয়ে একটা Service সংজ্ঞায়িত করা

Protocol Buffers (protobuf) আপনার API schema সংজ্ঞায়িত করে:

// proto/user/v1/user.proto
syntax = "proto3";

package user.v1;

option go_package = "github.com/yourname/myapp/gen/user/v1;userv1";

// Messages — your data types
message User {
  int32 id = 1;
  string email = 2;
  string name = 3;
  string role = 4;
  google.protobuf.Timestamp created_at = 5;
}

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

message CreateUserResponse {
  User user = 1;
}

message GetUserRequest {
  int32 id = 1;
}

message GetUserResponse {
  User user = 1;
}

message ListUsersRequest {
  int32 page = 1;
  int32 page_size = 2;
}

message ListUsersResponse {
  repeated User users = 1;
  int32 total_count = 2;
}

// Service — your API contract
service UserService {
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}
# Install protoc compiler and Go plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

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

Server Implement করা

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"

    userv1 "github.com/yourname/myapp/gen/user/v1"
)

type userServer struct {
    userv1.UnimplementedUserServiceServer  // Forward compatibility
    repo UserRepository
}

func (s *userServer) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.CreateUserResponse, error) {
    // Validate input
    if req.Email == "" {
        return nil, status.Error(codes.InvalidArgument, "email is required")
    }
    if req.Name == "" {
        return nil, status.Error(codes.InvalidArgument, "name is required")
    }

    // Create user
    user, err := s.repo.Create(ctx, req.Email, req.Name, req.Password)
    if err != nil {
        return nil, status.Errorf(codes.Internal, "failed to create user: %v", err)
    }

    return &userv1.CreateUserResponse{
        User: toProtoUser(user),
    }, nil
}

func (s *userServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
    user, err := s.repo.GetByID(ctx, int(req.Id))
    if err != nil {
        if errors.Is(err, ErrNotFound) {
            return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
        }
        return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
    }

    return &userv1.GetUserResponse{
        User: toProtoUser(user),
    }, nil
}

func (s *userServer) ListUsers(ctx context.Context, req *userv1.ListUsersRequest) (*userv1.ListUsersResponse, error) {
    pageSize := int(req.PageSize)
    if pageSize == 0 {
        pageSize = 20
    }
    offset := int(req.Page-1) * pageSize

    users, total, err := s.repo.List(ctx, pageSize, offset)
    if err != nil {
        return nil, status.Errorf(codes.Internal, "failed to list users: %v", err)
    }

    protoUsers := make([]*userv1.User, len(users))
    for i, u := range users {
        protoUsers[i] = toProtoUser(u)
    }

    return &userv1.ListUsersResponse{
        Users:      protoUsers,
        TotalCount: int32(total),
    }, nil
}

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

    grpcServer := grpc.NewServer()
    userv1.RegisterUserServiceServer(grpcServer, &userServer{repo: repo})

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

gRPC Client

generate করা কোড আপনাকে একটা type-safe client দেয়:

func main() {
    conn, err := grpc.NewClient("localhost:50051",
        grpc.WithTransportCredentials(insecure.NewCredentials()),
    )
    if err != nil {
        log.Fatalf("failed to connect: %v", err)
    }
    defer conn.Close()

    client := userv1.NewUserServiceClient(conn)

    // Create user
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    resp, err := client.CreateUser(ctx, &userv1.CreateUserRequest{
        Email:    "fatima@example.com",
        Name:     "Fatima",
        Password: "secret123",
    })
    if err != nil {
        // gRPC errors have status codes
        st, ok := status.FromError(err)
        if ok {
            log.Printf("gRPC error: code=%s, message=%s", st.Code(), st.Message())
        }
        return
    }

    fmt.Printf("Created user: %+v\n", resp.User)
}

gRPC Streaming

gRPC-র অন্যতম সেরা ফিচার — built-in streaming:

service AnalyticsService {
  // Server streaming — server sends multiple responses
  rpc WatchMetrics(WatchMetricsRequest) returns (stream MetricUpdate);

  // Client streaming — client sends multiple requests
  rpc UploadLogs(stream LogEntry) returns (UploadLogsResponse);

  // Bidirectional streaming — both sides stream
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
// Server streaming implementation
func (s *analyticsServer) WatchMetrics(req *pb.WatchMetricsRequest, stream pb.AnalyticsService_WatchMetricsServer) error {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            metric := collectMetric(req.MetricName)
            if err := stream.Send(&pb.MetricUpdate{
                Name:      metric.Name,
                Value:     metric.Value,
                Timestamp: timestamppb.Now(),
            }); err != nil {
                return err
            }
        case <-stream.Context().Done():
            return nil  // Client disconnected
        }
    }
}

gRPC Interceptor (Middleware)

// Unary interceptor (for single request/response RPCs)
func loggingInterceptor(
    ctx context.Context,
    req any,
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (any, error) {
    start := time.Now()
    resp, err := handler(ctx, req)
    slog.Info("gRPC request",
        "method", info.FullMethod,
        "duration_ms", time.Since(start).Milliseconds(),
        "error", err,
    )
    return resp, err
}

// Apply interceptors
grpcServer := grpc.NewServer(
    grpc.UnaryInterceptor(loggingInterceptor),
)

internal service-to-service communication-এর জন্য gRPC ব্যবহার করুন আর external/browser-facing API-র জন্য REST। অনেক কোম্পানি দুটোই চালায় — একটা REST gateway যা ভেতরে gRPC-তে translate করে। grpc-gateway-র মতো tool protobuf সংজ্ঞা থেকে এই translation স্বয়ংক্রিয় করে।

মূল শিক্ষা

  1. Protobuf contract সংজ্ঞায়িত করে.proto file হলো আপনার API-র একমাত্র সত্যের উৎস
  2. Code generation যেকোনো ভাষায় type-safe client আর server বানায়
  3. gRPC status code HTTP status code-এর জায়গা নেয় — codes.NotFound, codes.InvalidArgument
  4. Streaming built-in — WebSocket ছাড়াই server, client, আর bidirectional streaming
  5. Interceptor হলো gRPC middleware — logging, auth, metrics HTTP middleware-এর মতোই কাজ করে
  6. internal service-এর জন্য ব্যবহার করুন, external API-র জন্য REST — দুটোরই জন্য grpc-gateway-র সাথে মেলান