Skip to main content

Protobuf & gRPC

Protobuf

Protobuf (Protocol Buffers) is a language-neutral, platform-neutral data serialization format developed by Google. It’s designed for efficient communication and storage, especially in distributed systems.

  • Compact and Efficient: Protobuf uses binary serialization, which is smaller and faster than formats like JSON or XML.
  • Schema-based: Data is defined in .proto files using a schema, which ensures strict typing and compatibility. Less ambiguous and easier to use programmatically.
  • Cross-language: Supported in many languages like Go, Python, Java, etc.

gRPC

(Google Remote Procedure Calls) is a high-performance RPC framework that uses Protobuf for data serialization. It enables seamless communication between services, regardless of the programming language.

  • Supports Multiple Communication Types:
    • Unary (one request, one response)
    • Server-streaming (one request, multiple responses)
    • Client-streaming (multiple requests, one response)
    • Bi-directional streaming (multiple requests, multiple responses)
  • Built-in Code Generation: gRPC generates client and server stubs automatically from the .proto file.
  • HTTP/2 Support: Enables multiplexing, compression, and improved performance over HTTP/1.1.

Example

Define the Protobuf File

user.proto

syntax = "proto3";

package user;

service UserService {
// Unary RPC: Request user details
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}

message GetUserRequest {
string id = 1; // User ID
}

message GetUserResponse {
string id = 1;
string name = 2;
int32 age = 3;
}

Generate Code

Run the following command to generate the code (example for Go):

protoc --go_out=. --go-grpc_out=. user.proto

This generates:

  1. user.pb.go: Contains Protobuf message definitions.
  2. user_grpc.pb.go: Contains gRPC service definitions (interfaces for server and client).

Step 3: Implement the Server

server.go

package main

import (
"context"
"log"
"net"

"google.golang.org/grpc"
pb "path/to/generated/user" // Update with actual path
)

// Server struct implements the generated UserServiceServer interface
type Server struct {
pb.UnimplementedUserServiceServer
}

// Implement GetUser method
func (s *Server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
log.Printf("Received request for user ID: %s", req.Id)

// Mock user data
user := &pb.GetUserResponse{
Id: req.Id,
Name: "John Doe",
Age: 30,
}
return user, nil
}

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

grpcServer := grpc.NewServer()
pb.RegisterUserServiceServer(grpcServer, &Server{})

log.Println("Server is running on port 50051")
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}

Implement the Client

client.go

package main

import (
"context"
"log"
"time"

"google.golang.org/grpc"
pb "path/to/generated/user" // Update with actual path
)

func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to connect to server: %v", err)
}
defer conn.Close()

client := pb.NewUserServiceClient(conn)

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

// Call GetUser RPC
req := &pb.GetUserRequest{Id: "123"}
resp, err := client.GetUser(ctx, req)
if err != nil {
log.Fatalf("Error calling GetUser: %v", err)
}

log.Printf("User details: ID=%s, Name=%s, Age=%d", resp.Id, resp.Name, resp.Age)
}

Run the System

Start the gRPC server:

go run server.go

Run the client:

go run client.go

Output

On the server:

Received request for user ID: 123

On the client:

User details: ID=123, Name=John Doe, Age=30