Skip to Content
FeaturesModulesgRPC ServerArchitecture

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.

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

FeatureDescriptionConfiguration
MetricsRequest counts, response times, error ratesAlways ON
LoggingStructured console loggingAlways ON
DB LoggingGRPCRequestLog entriesobservability.log_to_db
CentrifugoWebSocket event publishingAuto-detected from config
Error HandlingMap exceptions to gRPC statusBuilt-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:

  1. Auth First - User context available for all observability features
  2. Single Observability Interceptor - Eliminates generator nesting bug
  3. Auto-Configuration - Centrifugo auto-detected from django-cfg config
  4. 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 Channel

Key Components

ComponentPurposeLibrary
RetryAutomatic retry with exponential backoffstamina
Circuit BreakerPrevent cascading failuresaiobreaker
Connection PoolChannel reuse for performanceBuilt-in
Structured LoggingJSON logging with contextstructlog

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:

  1. Service Discovery - Automatically discover all available services
  2. Method Introspection - Get method signatures and schemas
  3. Dynamic Message Creation - Create protobuf messages from JSON/dict
  4. 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 response

2. 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 → INTERNAL

Bidirectional Streaming Architecture

For long-running bidirectional connections, use BidirectionalStreamingService:

Key Components

ComponentResponsibility
InputProcessorReads messages via anext(), routes to handlers
OutputProcessorSends commands and ping/pong messages
ConnectionManagerTracks client state, handles cleanup
ResponseRegistryMaps 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

Next: Learn how to get started with gRPC or explore streaming patterns.

Last updated on