gRPC in 2026: Building High-Performance APIs for Modern Distributed Systems
on Grpc, Api design, Microservices, Protocol buffers, Performance
gRPC in 2026: Building High-Performance APIs for Modern Distributed Systems
REST has served us well, but for high-performance microservices communication, gRPC offers compelling advantages: binary serialization, strong typing, streaming support, and automatic code generation. Let’s dive deep into building production-ready gRPC services.
Photo by Jordan Harrison on Unsplash
Why gRPC in 2026?
Performance Comparison
| Feature | REST/JSON | gRPC/Protobuf |
|---|---|---|
| Serialization | Text (larger) | Binary (10x smaller) |
| Type Safety | Runtime validation | Compile-time checks |
| Streaming | Workarounds needed | Native support |
| Code Gen | OpenAPI (optional) | Built-in, mandatory |
| HTTP Version | HTTP/1.1 or 2 | HTTP/2 required |
Getting Started
Define Your Service
// order_service.proto
syntax = "proto3";
package ecommerce.orders.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
option go_package = "github.com/mycompany/ecommerce/orders/v1";
// Order represents a customer order
message Order {
string id = 1;
string customer_id = 2;
repeated OrderItem items = 3;
OrderStatus status = 4;
Money total = 5;
google.protobuf.Timestamp created_at = 6;
google.protobuf.Timestamp updated_at = 7;
}
message OrderItem {
string product_id = 1;
string product_name = 2;
int32 quantity = 3;
Money unit_price = 4;
}
message Money {
string currency_code = 1; // ISO 4217
int64 units = 2; // Whole units
int32 nanos = 3; // Nano units (10^-9)
}
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_CONFIRMED = 2;
ORDER_STATUS_SHIPPED = 3;
ORDER_STATUS_DELIVERED = 4;
ORDER_STATUS_CANCELLED = 5;
}
// Request/Response messages
message CreateOrderRequest {
string customer_id = 1;
repeated OrderItem items = 2;
ShippingAddress shipping_address = 3;
}
message CreateOrderResponse {
Order order = 1;
}
message GetOrderRequest {
string order_id = 1;
}
message ListOrdersRequest {
string customer_id = 1;
int32 page_size = 2;
string page_token = 3;
OrderStatus status_filter = 4;
}
message ListOrdersResponse {
repeated Order orders = 1;
string next_page_token = 2;
int32 total_count = 3;
}
// The Order Service
service OrderService {
// Unary RPC
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
rpc GetOrder(GetOrderRequest) returns (Order);
rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse);
// Server streaming - watch order status changes
rpc WatchOrder(GetOrderRequest) returns (stream Order);
// Client streaming - bulk order import
rpc BulkCreateOrders(stream CreateOrderRequest) returns (BulkCreateOrdersResponse);
// Bidirectional streaming - real-time order updates
rpc OrderUpdates(stream OrderUpdateRequest) returns (stream Order);
}
Generate Code
# Install protoc and plugins
brew install protobuf
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_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/order_service.proto
# Generate Python code
pip install grpcio-tools
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. \
proto/order_service.proto
Server Implementation
Go Server
package main
import (
"context"
"log"
"net"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
pb "github.com/mycompany/ecommerce/orders/v1"
)
type orderServer struct {
pb.UnimplementedOrderServiceServer
orders map[string]*pb.Order
}
func NewOrderServer() *orderServer {
return &orderServer{
orders: make(map[string]*pb.Order),
}
}
// Unary RPC implementation
func (s *orderServer) CreateOrder(
ctx context.Context,
req *pb.CreateOrderRequest,
) (*pb.CreateOrderResponse, error) {
// Validate request
if req.CustomerId == "" {
return nil, status.Error(codes.InvalidArgument, "customer_id is required")
}
if len(req.Items) == 0 {
return nil, status.Error(codes.InvalidArgument, "at least one item is required")
}
// Calculate total
var totalUnits int64
for _, item := range req.Items {
totalUnits += item.UnitPrice.Units * int64(item.Quantity)
}
// Create order
order := &pb.Order{
Id: generateOrderID(),
CustomerId: req.CustomerId,
Items: req.Items,
Status: pb.OrderStatus_ORDER_STATUS_PENDING,
Total: &pb.Money{
CurrencyCode: "USD",
Units: totalUnits,
},
CreatedAt: timestamppb.Now(),
UpdatedAt: timestamppb.Now(),
}
s.orders[order.Id] = order
return &pb.CreateOrderResponse{Order: order}, nil
}
// Server streaming implementation
func (s *orderServer) WatchOrder(
req *pb.GetOrderRequest,
stream pb.OrderService_WatchOrderServer,
) error {
order, ok := s.orders[req.OrderId]
if !ok {
return status.Error(codes.NotFound, "order not found")
}
// Send initial state
if err := stream.Send(order); err != nil {
return err
}
// Simulate status updates
statuses := []pb.OrderStatus{
pb.OrderStatus_ORDER_STATUS_CONFIRMED,
pb.OrderStatus_ORDER_STATUS_SHIPPED,
pb.OrderStatus_ORDER_STATUS_DELIVERED,
}
for _, newStatus := range statuses {
select {
case <-stream.Context().Done():
return stream.Context().Err()
case <-time.After(2 * time.Second):
order.Status = newStatus
order.UpdatedAt = timestamppb.Now()
if err := stream.Send(order); err != nil {
return err
}
}
}
return nil
}
// Bidirectional streaming
func (s *orderServer) OrderUpdates(
stream pb.OrderService_OrderUpdatesServer,
) error {
for {
req, err := stream.Recv()
if err != nil {
return err
}
order, ok := s.orders[req.OrderId]
if !ok {
continue
}
// Process update and send back
order.UpdatedAt = timestamppb.Now()
if err := stream.Send(order); err != nil {
return err
}
}
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// Create server with interceptors
server := grpc.NewServer(
grpc.ChainUnaryInterceptor(
loggingInterceptor,
recoveryInterceptor,
authInterceptor,
),
grpc.ChainStreamInterceptor(
streamLoggingInterceptor,
),
)
pb.RegisterOrderServiceServer(server, NewOrderServer())
log.Println("gRPC server listening on :50051")
if err := server.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Python Server
import grpc
from concurrent import futures
import time
from datetime import datetime
import order_service_pb2 as pb
import order_service_pb2_grpc as pb_grpc
from google.protobuf.timestamp_pb2 import Timestamp
class OrderService(pb_grpc.OrderServiceServicer):
def __init__(self):
self.orders = {}
def CreateOrder(self, request, context):
# Validate
if not request.customer_id:
context.abort(grpc.StatusCode.INVALID_ARGUMENT, "customer_id required")
# Create order
order_id = f"ORD-{int(time.time())}"
total_units = sum(
item.unit_price.units * item.quantity
for item in request.items
)
now = Timestamp()
now.GetCurrentTime()
order = pb.Order(
id=order_id,
customer_id=request.customer_id,
items=request.items,
status=pb.ORDER_STATUS_PENDING,
total=pb.Money(currency_code="USD", units=total_units),
created_at=now,
updated_at=now
)
self.orders[order_id] = order
return pb.CreateOrderResponse(order=order)
def WatchOrder(self, request, context):
"""Server streaming - yield order updates"""
order = self.orders.get(request.order_id)
if not order:
context.abort(grpc.StatusCode.NOT_FOUND, "Order not found")
# Send initial state
yield order
# Simulate updates
for new_status in [
pb.ORDER_STATUS_CONFIRMED,
pb.ORDER_STATUS_SHIPPED,
pb.ORDER_STATUS_DELIVERED
]:
time.sleep(2)
order.status = new_status
order.updated_at.GetCurrentTime()
yield order
def BulkCreateOrders(self, request_iterator, context):
"""Client streaming - receive multiple orders"""
created_orders = []
for request in request_iterator:
response = self.CreateOrder(request, context)
created_orders.append(response.order)
return pb.BulkCreateOrdersResponse(
orders=created_orders,
total_created=len(created_orders)
)
def serve():
server = grpc.server(
futures.ThreadPoolExecutor(max_workers=10),
interceptors=[LoggingInterceptor()]
)
pb_grpc.add_OrderServiceServicer_to_server(OrderService(), server)
server.add_insecure_port('[::]:50051')
server.start()
print("Server started on port 50051")
server.wait_for_termination()
if __name__ == '__main__':
serve()
Client Implementation
Photo by Markus Spiske on Unsplash
Go Client
package main
import (
"context"
"io"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "github.com/mycompany/ecommerce/orders/v1"
)
func main() {
// Connect with retry and load balancing
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{
"loadBalancingPolicy": "round_robin",
"methodConfig": [{
"name": [{"service": "ecommerce.orders.v1.OrderService"}],
"retryPolicy": {
"maxAttempts": 3,
"initialBackoff": "0.1s",
"maxBackoff": "1s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE", "DEADLINE_EXCEEDED"]
}
}]
}`),
)
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewOrderServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Unary call
resp, err := client.CreateOrder(ctx, &pb.CreateOrderRequest{
CustomerId: "CUST-001",
Items: []*pb.OrderItem{
{
ProductId: "PROD-001",
ProductName: "Widget",
Quantity: 2,
UnitPrice: &pb.Money{CurrencyCode: "USD", Units: 29},
},
},
})
if err != nil {
log.Fatalf("CreateOrder failed: %v", err)
}
log.Printf("Created order: %s", resp.Order.Id)
// Server streaming
stream, err := client.WatchOrder(ctx, &pb.GetOrderRequest{
OrderId: resp.Order.Id,
})
if err != nil {
log.Fatalf("WatchOrder failed: %v", err)
}
for {
order, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("stream error: %v", err)
}
log.Printf("Order status: %s", order.Status)
}
}
Advanced Patterns
Interceptors (Middleware)
func loggingInterceptor(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
start := time.Now()
// Call the handler
resp, err := handler(ctx, req)
// Log after
duration := time.Since(start)
status := codes.OK
if err != nil {
status = grpc.Code(err)
}
log.Printf(
"method=%s duration=%s status=%s",
info.FullMethod,
duration,
status,
)
return resp, err
}
func authInterceptor(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
// Extract metadata
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "no metadata")
}
// Validate token
tokens := md.Get("authorization")
if len(tokens) == 0 {
return nil, status.Error(codes.Unauthenticated, "no token")
}
userID, err := validateToken(tokens[0])
if err != nil {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
// Add user to context
ctx = context.WithValue(ctx, "user_id", userID)
return handler(ctx, req)
}
Health Checks
import "google.golang.org/grpc/health/grpc_health_v1"
type healthServer struct {
grpc_health_v1.UnimplementedHealthServer
}
func (s *healthServer) Check(
ctx context.Context,
req *grpc_health_v1.HealthCheckRequest,
) (*grpc_health_v1.HealthCheckResponse, error) {
// Check dependencies
if !isDatabaseHealthy() {
return &grpc_health_v1.HealthCheckResponse{
Status: grpc_health_v1.HealthCheckResponse_NOT_SERVING,
}, nil
}
return &grpc_health_v1.HealthCheckResponse{
Status: grpc_health_v1.HealthCheckResponse_SERVING,
}, nil
}
gRPC-Gateway (REST Transcoding)
import "google/api/annotations.proto";
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse) {
option (google.api.http) = {
post: "/v1/orders"
body: "*"
};
}
rpc GetOrder(GetOrderRequest) returns (Order) {
option (google.api.http) = {
get: "/v1/orders/{order_id}"
};
}
}
Best Practices
- Version your protos: Use
v1,v2packages - Use field numbers wisely: Never reuse deleted field numbers
- Set deadlines: Always use context with timeout
- Handle streaming errors: Check for
io.EOFproperly - Implement health checks: Required for Kubernetes deployments
- Use interceptors: For cross-cutting concerns
Conclusion
gRPC is the modern choice for high-performance microservices communication. Its strong typing, efficient serialization, and native streaming support make it ideal for distributed systems. Combined with Protocol Buffers, you get a robust contract-first API development experience.
Start with simple unary RPCs, then explore streaming patterns as your needs grow. The investment in learning gRPC pays off in performance, reliability, and developer experience.
For more on microservices architecture, check out our guides on Kubernetes and observability with OpenTelemetry.
이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)
