| name | generate-grpc-service |
| description | Generate production-ready gRPC services with Protocol Buffers
|
| shortcut | grpc |
Generate gRPC Service
Automatically generate high-performance gRPC services with Protocol Buffer definitions, streaming support, load balancing, and comprehensive service implementations for multiple programming languages.
When to Use This Command
Use /generate-grpc-service when you need to:
- Build high-performance microservices with binary protocol
- Implement real-time bidirectional streaming communication
- Create strongly-typed service contracts across languages
- Build internal services requiring minimal latency
- Support multiple programming languages with single definition
- Implement efficient mobile/IoT communication protocols
DON'T use this when:
- Building browser-based web applications (limited browser support)
- Simple REST APIs suffice (gRPC adds complexity)
- Working with teams unfamiliar with Protocol Buffers
- Debugging tools are limited in your environment
Design Decisions
This command implements gRPC with Protocol Buffers v3 as the primary approach because:
- Binary protocol offers 20-30% better performance than JSON
- Built-in code generation for 10+ languages
- Native support for streaming in all RPC patterns
- Strong typing prevents runtime errors
- Backward compatibility through field numbering
- Built-in service discovery and load balancing
Alternative considered: Apache Thrift
- Similar performance characteristics
- Less ecosystem support
- Fewer language bindings
- Recommended for Facebook ecosystem
Alternative considered: GraphQL with subscriptions
- Better for public APIs
- More flexible queries
- Higher overhead
- Recommended for client-facing APIs
Prerequisites
Before running this command:
- Protocol Buffer compiler (protoc) installed
- Language-specific gRPC tools installed
- Understanding of Protocol Buffer syntax
- Service architecture defined
- Authentication strategy determined
Implementation Process
Step 1: Define Service Contract
Create comprehensive .proto files with service definitions and message types.
Step 2: Generate Language Bindings
Compile Protocol Buffers to target language code with gRPC plugins.
Step 3: Implement Service Logic
Build server-side implementations for all RPC methods.
Step 4: Add Interceptors
Implement cross-cutting concerns like auth, logging, and error handling.
Step 5: Configure Production Settings
Set up TLS, connection pooling, and load balancing.
Output Format
The command generates:
proto/service.proto - Protocol Buffer definitions
server/ - Server implementation with all RPC methods
client/ - Client library with connection management
interceptors/ - Authentication, logging, metrics interceptors
config/ - TLS certificates and configuration
docs/api.md - Service documentation
Code Examples
Example 1: E-commerce Service with All RPC Patterns
// proto/ecommerce.proto
syntax = "proto3";
package ecommerce.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
// Service definition with all RPC patterns
service ProductService {
// Unary RPC
rpc GetProduct(GetProductRequest) returns (Product);
// Server streaming
rpc ListProducts(ListProductsRequest) returns (stream Product);
// Client streaming
rpc ImportProducts(stream Product) returns (ImportSummary);
// Bidirectional streaming
rpc WatchInventory(stream InventoryUpdate) returns (stream InventoryChange);
// Batch operations
rpc BatchGetProducts(BatchGetProductsRequest) returns (BatchGetProductsResponse);
}
// Message definitions
message Product {
string id = 1;
string name = 2;
string description = 3;
double price = 4;
int32 inventory = 5;
repeated string categories = 6;
map<string, string> metadata = 7;
google.protobuf.Timestamp created_at = 8;
google.protobuf.Timestamp updated_at = 9;
enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_ACTIVE = 1;
STATUS_DISCONTINUED = 2;
STATUS_OUT_OF_STOCK = 3;
}
Status status = 10;
}
message GetProductRequest {
string product_id = 1;
repeated string fields = 2; // Field mask for partial responses
}
message ListProductsRequest {
string category = 1;
int32 page_size = 2;
string page_token = 3;
string order_by = 4;
message Filter {
double min_price = 1;
double max_price = 2;
repeated string tags = 3;
}
Filter filter = 5;
}
message ImportSummary {
int32 total_received = 1;
int32 successful = 2;
int32 failed = 3;
repeated ImportError errors = 4;
}
message ImportError {
int32 index = 1;
string product_id = 2;
string error = 3;
}
message InventoryUpdate {
string product_id = 1;
int32 quantity_change = 2;
string warehouse_id = 3;
}
message InventoryChange {
string product_id = 1;
int32 old_quantity = 2;
int32 new_quantity = 3;
google.protobuf.Timestamp timestamp = 4;
string triggered_by = 5;
}
message BatchGetProductsRequest {
repeated string product_ids = 1;
repeated string fields = 2;
}
message BatchGetProductsResponse {
repeated Product products = 1;
repeated string not_found = 2;
}
package main
import (
"context"
"crypto/tls"
"fmt"
"io"
"log"
"net"
"sync"
"time"
pb "github.com/company/ecommerce/proto"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
type productServer struct {
pb.UnimplementedProductServiceServer
mu sync.RWMutex
products map[string]*pb.Product
watchers map[string]chan *pb.InventoryChange
}
func (s *productServer) GetProduct(
ctx context.Context,
req *pb.GetProductRequest,
) (*pb.Product, error) {
if md, ok := metadata.FromIncomingContext(ctx); ok {
if traceID := md.Get("trace-id"); len(traceID) > 0 {
log.Printf("GetProduct request - trace: %s", traceID[0])
}
}
s.mu.RLock()
product, exists := s.products[req.ProductId]
s.mu.RUnlock()
if !exists {
return , status.Errorf(
codes.NotFound,
,
req.ProductId,
)
}
(req.Fields) > {
applyFieldMask(product, req.Fields),
}
product,
}
ListProducts(
req *pb.ListProductsRequest,
stream pb.ProductService_ListProductsServer,
) {
s.mu.RLock()
s.mu.RUnlock()
count :=
_, product := s.products {
!matchesFilter(product, req) {
}
err := stream.Send(product); err != {
status.Errorf(
codes.Internal,
,
err,
)
}
count++
req.PageSize > && count >= (req.PageSize) {
}
time.Sleep( * time.Millisecond)
}
}
ImportProducts(
stream pb.ProductService_ImportProductsServer,
) {
summary pb.ImportSummary
errors []*pb.ImportError
index :=
{
product, err := stream.Recv()
err == io.EOF {
summary.Errors = errors
stream.SendAndClose(&summary)
}
err != {
status.Errorf(
codes.Internal,
,
err,
)
}
summary.TotalReceived++
err := validateProduct(product); err != {
summary.Failed++
errors = (errors, &pb.ImportError{
Index: (index),
ProductId: product.Id,
Error: err.Error(),
})
} {
s.mu.Lock()
s.products[product.Id] = product
s.mu.Unlock()
summary.Successful++
}
index++
}
}
WatchInventory(
stream pb.ProductService_WatchInventoryServer,
) {
changeChan := ( *pb.InventoryChange, )
clientID := generateClientID()
s.mu.Lock()
s.watchers[clientID] = changeChan
s.mu.Unlock()
{
s.mu.Lock()
(s.watchers, clientID)
s.mu.Unlock()
(changeChan)
}()
errChan := ( , )
{
{
update, err := stream.Recv()
err == io.EOF {
errChan <-
}
err != {
errChan <- err
}
err := s.processInventoryUpdate(update); err != {
log.Printf(, err)
}
change := &pb.InventoryChange{
ProductId: update.ProductId,
NewQuantity: s.getInventory(update.ProductId),
Timestamp: timestamppb.Now(),
TriggeredBy: clientID,
}
s.broadcastChange(change)
}
}()
{
change := changeChan {
err := stream.Send(change); err != {
errChan <- err
}
}
}()
<-errChan
}
({}, ) {
md, ok := metadata.FromIncomingContext(ctx)
!ok {
, status.Error(codes.Unauthenticated, )
}
tokens := md.Get()
(tokens) == {
, status.Error(codes.Unauthenticated, )
}
!isValidToken(tokens[]) {
, status.Error(codes.Unauthenticated, )
}
handler(ctx, req)
}
{
cert, err := tls.LoadX509KeyPair(, )
err != {
log.Fatalf(, err)
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
}
creds := credentials.NewTLS(config)
opts := []grpc.ServerOption{
grpc.Creds(creds),
grpc.UnaryInterceptor(authInterceptor),
grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: * time.Minute,
Time: * time.Minute,
Timeout: * time.Second,
}),
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: * time.Second,
PermitWithoutStream: ,
}),
grpc.MaxConcurrentStreams(),
}
server := grpc.NewServer(opts...)
pb.RegisterProductServiceServer(server, &productServer{
products: ([]*pb.Product),
watchers: ([] *pb.InventoryChange),
})
lis, err := net.Listen(, )
err != {
log.Fatalf(, err)
}
log.Println()
err := server.Serve(lis); err != {
log.Fatalf(, err)
}
}
Example 2: Python Client with Retry and Load Balancing
import grpc
import asyncio
import logging
from typing import List, Optional, AsyncIterator
from concurrent import futures
from grpc import aio
import backoff
from proto import ecommerce_pb2 as pb
from proto import ecommerce_pb2_grpc as pb_grpc
logger = logging.getLogger(__name__)
class ProductClient:
"""Enhanced gRPC client with retry, load balancing, and connection pooling."""
def __init__(
self,
servers: List[str],
api_key: Optional[str] = None,
use_tls: bool = True,
pool_size: int = 10
):
self.servers = servers
self.api_key = api_key
self.use_tls = use_tls
self.pool_size = pool_size
self.channels = []
self.stubs = []
self._round_robin_counter = 0
self._setup_channels()
def _setup_channels(self):
server .servers:
_ (.pool_size // (.servers)):
.use_tls:
(, ) f:
client_cert = f.read()
(, ) f:
client_key = f.read()
(, ) f:
ca_cert = f.read()
credentials = grpc.ssl_channel_credentials(
root_certificates=ca_cert,
private_key=client_key,
certificate_chain=client_cert
)
channel = aio.secure_channel(
server,
credentials,
options=[
(, ),
(, ),
(, ),
(, ),
]
)
:
channel = aio.insecure_channel(
server,
options=[
(, ),
(, ),
]
)
.channels.append(channel)
.stubs.append(pb_grpc.ProductServiceStub(channel))
() -> pb_grpc.ProductServiceStub:
stub = .stubs[._round_robin_counter]
._round_robin_counter = (._round_robin_counter + ) % (.stubs)
stub
() -> []:
metadata = []
.api_key:
metadata.append((, ))
metadata.append((, ._generate_trace_id()))
metadata
() -> pb.Product:
request = pb.GetProductRequest(
product_id=product_id,
fields=fields []
)
:
response = ._get_stub().GetProduct(
request,
metadata=._get_metadata(),
timeout=
)
response
grpc.RpcError e:
logger.error()
() -> AsyncIterator[pb.Product]:
request = pb.ListProductsRequest(
category=category ,
page_size=page_size
)
min_price max_price :
request..CopyFrom(pb.ListProductsRequest.Filter(
min_price=min_price ,
max_price=max_price ()
))
:
stream = ._get_stub().ListProducts(
request,
metadata=._get_metadata(),
timeout=
)
product stream:
product
grpc.RpcError e:
logger.error()
() -> pb.ImportSummary:
():
product products:
product
asyncio.sleep()
:
response = ._get_stub().ImportProducts(
generate_products(),
metadata=._get_metadata(),
timeout=
)
response.failed > :
logger.warning(
)
response
grpc.RpcError e:
logger.error()
() -> AsyncIterator[pb.InventoryChange]:
:
stream = ._get_stub().WatchInventory(
metadata=._get_metadata()
)
send_task = asyncio.create_task(._send_updates(stream, updates))
:
change stream:
change
:
send_task.cancel()
grpc.RpcError e:
logger.error()
():
:
update updates:
stream.write(update)
stream.done_writing()
asyncio.CancelledError:
():
close_tasks = [channel.close() channel .channels]
asyncio.gather(*close_tasks)
() -> :
uuid
(uuid.uuid4())
():
client = ProductClient(
servers=[
,
,
],
api_key=,
use_tls=
)
:
product = client.get_product()
()
product client.list_products(
category=,
min_price=,
max_price=
):
()
products_to_import = [
pb.Product(=, name=, price=)
i ()
]
summary = client.import_products(products_to_import)
()
():
i ():
pb.InventoryUpdate(
product_id=,
quantity_change=,
warehouse_id=
)
asyncio.sleep()
change client.watch_inventory(generate_updates()):
()
:
client.close()
__name__ == :
asyncio.run(main())
Example 3: Node.js Implementation with Health Checking
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
const PROTO_PATH = path.join(__dirname, '../proto/ecommerce.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
const ecommerce = protoDescriptor.ecommerce.v1;
const health = require('grpc-health-check');
const healthImpl = new health.Implementation({
'': 'SERVING',
'ecommerce.v1.ProductService': 'SERVING'
});
class ProductService {
constructor() {
. = ();
. = ();
}
() {
{ product_id } = call.;
product = ..(product_id);
(!product) {
({
: grpc..,
:
});
;
}
(, product);
}
() {
{ category, page_size } = call.;
count = ;
( [id, product] .) {
(category && product..(category) === -) {
;
}
call.(product);
count++;
(page_size > && count >= page_size) {
;
}
( (resolve, ));
}
call.();
}
}
() {
server = grpc.({
: ,
: * *
});
server.(
ecommerce..,
()
);
server.(health., healthImpl);
server.(
,
grpc..(),
{
(err) {
.(, err);
;
}
.();
server.();
}
);
}
();
Error Handling
| Error | Cause | Solution |
|---|
| "Failed to compile proto" | Invalid Protocol Buffer syntax | Validate with protoc --lint |
| "Connection refused" | Server not running or wrong port | Check server status and port |
| "Deadline exceeded" | Request timeout | Increase timeout or optimize operation |
| "Resource exhausted" | Rate limiting or quota exceeded | Implement backoff and retry |
| "Unavailable" | Server temporarily down | Implement circuit breaker pattern |
Configuration Options
Server Options
MaxConcurrentStreams: Limit concurrent streams per connection
MaxReceiveMessageSize: Maximum message size (default 4MB)
KeepaliveParams: Connection health monitoring
ConnectionTimeout: Maximum idle time before closing
Client Options
LoadBalancingPolicy: round_robin, pick_first, grpclb
WaitForReady: Block until server available
Retry: Automatic retry configuration
Interceptors: Add cross-cutting concerns
Best Practices
DO:
- Use field numbers consistently for backward compatibility
- Implement proper error codes and messages
- Add request deadlines for all RPCs
- Use streaming for large datasets
- Implement health checking endpoints
- Version your services properly
DON'T:
- Change field numbers in proto files
- Use gRPC for browser clients without proxy
- Ignore proper error handling
- Send large messages without streaming
- Skip TLS in production
- Use synchronous calls for long operations
Performance Considerations
- Binary protocol reduces bandwidth by 20-30% vs JSON
- HTTP/2 multiplexing eliminates head-of-line blocking
- Connection pooling reduces handshake overhead
- Streaming prevents memory exhaustion with large datasets
- Protocol Buffers provide 3-10x faster serialization than JSON
Security Considerations
- Always use TLS in production with mutual authentication
- Implement token-based authentication via metadata
- Use interceptors for consistent auth across services
- Validate all input according to proto definitions
- Implement rate limiting per client
- Use service accounts for service-to-service auth
Related Commands
/rest-api-generator - Generate REST APIs
/graphql-server-builder - Build GraphQL servers
/api-gateway-builder - Create API gateways
/webhook-handler-creator - Handle webhooks
/websocket-server-builder - WebSocket servers
Version History
- v1.0.0 (2024-10): Initial implementation with Go, Python, Node.js support
- Planned v1.1.0: Add Rust and Java implementations with advanced load balancing