executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
grpc"gRPC in Remote Procedure Call - cloud native architecture, patterns" pitfalls, and best practices
Independent GitHub snapshot · Automated classification · Original documentation governs
name: grpc compatibility: opencode completeness: 95 content-types:
syntax = "proto3";
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (stream User);
}
message GetUserRequest {
string user_id = 1;
}
message User {
string id = 1;
string email = 2;
string name = 3;
}
Client Application
↓ (gRPC stub)
HTTP/2 Connection
↓ (Protocol Buffers serialization)
Service Mesh (Envoy)
↓ (mutual TLS)
Server Application
↓ (gRPC server)
Business Logic
Client → [Request] → Server → [Response] → Client
Client → [Request] → Server → [Response 1] → Client
→ [Response 2] → Client
→ [Response 3] → Client
Client → [Request 1] → Server
→ [Request 2] → [Aggregate Response] → Client
→ [Request 3] →
Client → [Req 1] → [Resp 1] ← Server
→ [Req 2] → [Resp 2] ←
→ [Req 3] → ←
.proto definitions before implementationapiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- port: 50051
targetPort: 50051
name: grpc
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- match:
- uri:
prefix: /UserService
route:
- destination:
host: user-service
port:
number: 50051
# Versioned package names enable evolution
package api.users.v1;
package api.users.v2;
# Nested messages for organization
message User {
string id = 1;
Profile profile = 2;
}
message Profile {
string name = 1;
string avatar_url = 2;
}
grpc:
target: user-service:50051
keepalive:
time: 30s
timeout: 10s
retry:
max_attempts: 3
initial_backoff: 100ms
max_backoff: 1s
backoff_multiplier: 1.5
load_balancing: round_robin
grpc:
port: 50051
max_concurrent_streams: 100
max_metadata_size: 8192
keepalive:
min_time: 30s
timeout: 10s
reflection:
enabled: true
import "google/api/annotations.proto";
service UserService {
rpc GetUser(GetUserRequest) returns (User) {
option (google.api.http) = {
get: "/v1/users/{user_id}"
};
}
}
Problem: Connections drop in environments with idle connection cleanup (load balancers, firewalls).
Solution:
# Client keepalive
grpc:
keepalive:
time: 30s
timeout: 10s
permit_without_stream: true
Problem: Streaming endpoints without proper limits cause resource exhaustion.
Solution:
Problem: gRPC defaults may not enforce TLS or proper authentication.
Solution:
Problem: Large Protocol Buffer messages impact memory and CPU.
Solutions:
Problem: Clients that don't read streaming responses cause memory leaks.
Solution:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
stream, err := client.ListUsers(ctx, &ListUsersRequest{})
if err != nil {
return err
}
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
user, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
processUser(user)
}
}
Problem: Too many concurrent connections exhaust system resources.
Solutions:
Problem: gRPC traffic is binary and harder to inspect than HTTP/JSON.
Solutions:
Problem: Breaking changes in .proto definitions cause runtime failures.
Solutions:
optional keyword for nullable fieldsProblem: Services don't discover each other dynamically in Kubernetes.
Solutions:
Problem: Services accept unauthenticated requests.
Solution:
// Server-side interceptor
func AuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
metadata, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "missing metadata")
}
token := metadata.Get("authorization")
if len(token) == 0 {
return nil, status.Error(codes.Unauthenticated, "missing token")
}
claims, err := validateToken(token[0])
if err != nil {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
ctx = context.WithValue(ctx, "claims", claims)
return handler(ctx, req)
}
Problem: Authentication occurs but authorization is not enforced.
Solution: Implement role-based access control at service level.
Problem: Authentication tokens in metadata logged accidentally.
Solution:
// Go example
import "google.golang.org/grpc"
conn, err := grpc.Dial(
target,
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(1024*1024*10),
grpc.MaxCallSendMsgSize(1024*1024*10),
),
grpc.WithResolvers(
// Custom resolver if needed
),
)
// Go example with proper error handling
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
// Early exit for invalid input
if req.UserId == "" {
return nil, status.Error(codes.InvalidArgument, "user_id is required")
}
user, err := s.userService.GetByID(req.UserId)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.NotFound, "user %s not found", req.UserId)
}
return user, nil
}
# ✅ GOOD — gRPC streaming client using grpcurl CLI
# Test unary RPC
grpcurl -plaintext localhost:50051 list myapp.v1.MyService
# Test ListUsers streaming RPC
grpcurl -plaintext -d '{"page_size": 10}' localhost:50051 myapp.v1.MyService/ListUsers | jq .
# Test with TLS
grpcurl -d '{"user_id": "123"}' -cacert ca.crt localhost:50051 myapp.v1.MyService/GetUser | jq .
# Stream output to file for processing
grpcurl -plaintext localhost:50051 myapp.v1.MyService/ListUsers | jq -c '.users[] | .email' > users.txt
# Test with headers/metadata
grpcurl -H "Authorization: Bearer token123" -plaintext localhost:50051 myapp.v1.MyService/ListUsers | jq .
# Debug stream with verbose output
grpcurl -v -plaintext localhost:50051 myapp.v1.MyService/ListUsers
# ✅ GOOD — gRPC streaming with bash while loop
grpcurl -plaintext localhost:50051 myapp.v1.MyService/ListUsers | while IFS= read -r line; do
echo "$line" | jq -r '.email, .name' 2>/dev/null
done || echo "Stream error: connection failed"
// Go context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Pass context to all gRPC calls
response, err := client.GetUser(ctx, &pb.GetUserRequest{UserId: id})
// Include trace context
span := trace.SpanFromContext(ctx)
span.SetAttributes(
attribute.String("grpc.service", info.FullMethod),
attribute.String("grpc.method", filepath.Base(info.FullMethod)),
)
.proto filesprotoc to generate stubs# GitHub Actions example
jobs:
build:
steps:
- uses: actions/checkout@v3
- name: Install protoc
uses: arduino/setup-protoc@v1
- name: Generate stubs
run: make generate
- name: Test
run: make test
- name: Lint protobuf
run: make protoc-lint
.proto files| Term | Definition |
| related-skills: null
| Stub | Client-side proxy that makes gRPC calls appear as local method calls |
| Server | Service implementation that receives and processes gRPC requests |
| Channel | Connection management object handle |
| Context | Request-scoped data including deadline, cancellation, and metadata |
| Reflection | Protocol for clients to query service capabilities at runtime |
| StatusCode | Standardized status codes (OK, CANCELLED, UNKNOWN, etc.) |
| Message | Structured data unit in Protocol Buffers |
| Service | Interface defining RPC methods in .proto file |