gRPC Architecture & Design
This document explains the architecture and design patterns behind Django-CFG’s gRPC integration.
🏗️ System Architecture
High-Level Overview
🔄 Request Flow Architecture
Complete Request Lifecycle
Error Flow
🧩 Component Architecture
1. Interceptors Pipeline
Architecture Update (v1.5+): Interceptors have been consolidated into ObservabilityInterceptor to fix bidirectional streaming bugs. The old 5-layer architecture caused premature StopAsyncIteration after ~15 messages.
Current Architecture (Recommended)
Why ObservabilityInterceptor?
Each interceptor wraps request_iterator in an async generator. With 5 separate interceptors, buffer backpressure caused premature StopAsyncIteration in bidirectional streaming after ~15 messages.
Solution: Consolidate all observability features into ONE interceptor with a SINGLE counting_iterator().
ObservabilityInterceptor Features
| Feature | Description | Configuration |
|---|---|---|
| Metrics | Request counts, response times, error rates | Always ON |
| Logging | Structured console logging | Always ON |
| DB Logging | GRPCRequestLog entries | observability.log_to_db |
| Centrifugo | WebSocket event publishing | Auto-detected from config |
| Error Handling | Map exceptions to gRPC status | Built-in |
Configuration
from django_cfg import GRPCConfig, GRPCObservabilityConfig
grpc = GRPCConfig(
enabled=True,
observability=GRPCObservabilityConfig(
log_to_db=True, # Enable DB logging
log_streaming=False, # Don't log bidi streams (creates 'pending' entries)
log_errors_only=False, # Log all requests, not just errors
sampling_rate=1.0, # Log 100% of requests
),
)Key Design Decisions:
- Auth First - User context available for all observability features
- Single Observability Interceptor - Eliminates generator nesting bug
- Auto-Configuration - Centrifugo auto-detected from django-cfg config
- Streaming-Safe - No timeout on
anext(), uses ping/keepalive instead
2. Service Discovery System
3. Base Service Classes Hierarchy
🔐 Authentication Architecture
API Key Flow
Public vs Protected Methods
📊 Monitoring Architecture
Request Logging System
Statistics & Metrics
🔁 Resilience Architecture
The resilience layer wraps gRPC calls with production-grade patterns:
┌─────────────────────────┐
│ Application │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ Retry Layer │ @retry_grpc (stamina)
│ 5 attempts, 30s max │ Exponential backoff
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ Circuit Breaker │ GRPCCircuitBreaker (aiobreaker)
│ 5 failures = open │ 60s reset timeout
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ Connection Pool │ GRPCChannelPool
│ Channel reuse │ Configurable size
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ Structured Logging │ structlog with context
│ JSON/Console output │ Request tracing
└───────────┬─────────────┘
│
▼
gRPC ChannelKey Components
| Component | Purpose | Library |
|---|---|---|
| Retry | Automatic retry with exponential backoff | stamina |
| Circuit Breaker | Prevent cascading failures | aiobreaker |
| Connection Pool | Channel reuse for performance | Built-in |
| Structured Logging | JSON logging with context | structlog |
Using Resilient Client
from django_cfg.modules.django_grpc.services.client import AsyncResilientGRPCClient
async with AsyncResilientGRPCClient(
host="localhost",
port=50051,
enable_retry=True,
enable_circuit_breaker=True,
use_pool=True,
) as client:
result = await client.call_method(
service_name="myservice.MyService",
method_name="GetData",
request_data={"id": "123"},
request_class=MyRequest,
response_class=MyResponse,
)Learn more: Resilience Patterns | Connection Pooling
🔄 Phase 4: Dynamic Invocation
Dynamic gRPC Client Architecture
Key Features:
- Service Discovery - Automatically discover all available services
- Method Introspection - Get method signatures and schemas
- Dynamic Message Creation - Create protobuf messages from JSON/dict
- No Proto Files Required - Use reflection to understand service contracts
Learn more: Dynamic Invocation Guide
🎯 Design Patterns
1. Interceptor Pattern
Purpose: Cross-cutting concerns (logging, auth, metrics) without modifying service code.
Implementation:
class MyInterceptor(grpc.ServerInterceptor):
def intercept_service(self, continuation, handler_call_details):
# Pre-processing
# ...
# Call next interceptor/service
response = continuation(handler_call_details)
# Post-processing
# ...
return response2. Service Discovery Pattern
Purpose: Automatic registration of gRPC services from Django apps.
Key Components:
- Module scanning (grpc_services.py, grpc_handlers.py)
- Lazy loading support
- Convention over configuration
3. Django Integration Pattern
Purpose: Seamless access to Django features in gRPC services.
Integration Points:
- ORM via models
- User authentication via API keys
- Permissions via Django auth
- Admin interface for monitoring
- Signals for events
4. Error Mapping Pattern
Purpose: Convert Django exceptions to appropriate gRPC status codes.
Mapping:
Django Exception → gRPC Status Code
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ValidationError → INVALID_ARGUMENT
ObjectDoesNotExist → NOT_FOUND
PermissionDenied → PERMISSION_DENIED
NotImplementedError → UNIMPLEMENTED
TimeoutError → DEADLINE_EXCEEDED
Exception → INTERNALBidirectional Streaming Architecture
For long-running bidirectional connections, use BidirectionalStreamingService:
Key Components
| Component | Responsibility |
|---|---|
InputProcessor | Reads messages via anext(), routes to handlers |
OutputProcessor | Sends commands and ping/pong messages |
ConnectionManager | Tracks client state, handles cleanup |
ResponseRegistry | Maps command IDs to pending responses |
Critical Configuration
Never set connection_timeout - it causes premature StopAsyncIteration in grpcio. See Troubleshooting.
BidirectionalStreamingConfig(
streaming_mode=StreamingMode.ANEXT,
ping_strategy=PingStrategy.INTERVAL,
ping_interval=5.0,
ping_timeout=180.0,
connection_timeout=None, # CRITICAL!
)Learn more: Streaming Patterns
Performance Considerations
Threading Model
Configuration:
from django_cfg import GRPCServerConfig, GRPCKeepaliveConfig
GRPCServerConfig(
# Keepalive for connection health
keepalive=GRPCKeepaliveConfig.for_streaming(),
# Message size limits
max_send_message_length=50 * 1024 * 1024, # 50MB
max_receive_message_length=50 * 1024 * 1024,
)Database Optimization
- Connection Pooling - Reuse database connections
- Select Related / Prefetch Related - Reduce N+1 queries
- Indexed Fields - Fast lookups on service/method/status
- Async Logging - Non-blocking request logging
Related Documentation
- Getting Started - Configure gRPC in your project
- Resilience Patterns - Retry, circuit breaker, logging
- Connection Pooling - Channel reuse
- Betterproto2 - Modern Python gRPC
- Streaming Patterns - Bidirectional streaming patterns
- Troubleshooting - Common issues and solutions
- Configuration - Configure gRPC settings
- FAQ - Common questions and answers
Next: Learn how to get started with gRPC or explore streaming patterns.