A reference for the architectural patterns used in this kit and when to
apply them in production.
What: A single entry point that routes requests to downstream services
and handles cross-cutting concerns (auth, rate limiting, logging).
When to use: Always. Even with two services, a gateway simplifies
client integration and centralises security enforcement.
Client β API Gateway β User Service
β Order Service
Trade-offs:
What: Each microservice owns its data store. Services never access
another service's database directly.
When to use: Default choice. Sharing a database couples services at
the schema level, defeating the purpose of microservices.
Implementation:
# user-service owns its own SQLite / Postgres
DATABASE_URL=sqlite:///./users.db
# order-service has its own store
DATABASE_URL=sqlite:///./orders.dbTo query data owned by another service, use the service client (HTTP)
or listen for domain events.
What: Services communicate by publishing and subscribing to events
rather than making synchronous calls.
When to use: When a state change in one service should trigger
behaviour in another without tight coupling.
Order Service ββpublishesβββΆ "order.created"
β
User Service βββsubscribesβββββββββ
(sends confirmation email)
Transport options (swap the EventBus implementation):
| Transport | Latency | Durability | Complexity |
|---|---|---|---|
| In-process | ~0 ms | None | Trivial |
| Redis Streams | ~1 ms | Optional | Low |
| RabbitMQ | ~2 ms | Yes | Medium |
| Apache Kafka | ~5 ms | Yes | High |
What: Wraps inter-service HTTP calls. After N consecutive failures,
the circuit "opens" and immediately rejects requests for a recovery
period, preventing cascading failures.
States:
ββββββββββββ N failures ββββββββββββ
β CLOSED β ββββββββββββββββββΆ β OPEN β
β (normal) β β (reject) β
ββββββββββββ ββββββ¬ββββββ
β² β recovery timeout
β 1 success β
βββββββββββββββββββββ βββββββββββΌβββββββ
β HALF-OPEN β
β (probe 1 req) β
ββββββββββββββββββ
Configuration (in ServiceClient):
client = ServiceClient(
base_url="http://user-service:8001",
circuit_failure_threshold=5, # open after 5 failures
circuit_recovery_timeout=30.0, # try again after 30s
)What: A unique ID assigned to each external request, propagated
through all inter-service calls via the x-correlation-id header.
Why: In production, a single user click can trigger requests across
5+ services. Without a correlation ID, piecing together the log trail
is impossible.
[a1b2c3d4] POST /api/orders β gateway
[a1b2c3d4] GET /users/usr_abc123 β order-service β user-service
[a1b2c3d4] PUBLISH order.created β order-service
Implementation: The CorrelationMiddleware in shared/tracing.py
handles ID generation and propagation automatically.
What: Each service exposes a /health endpoint. The gateway
aggregates them into a composite health report.
Kubernetes-style probes:
| Probe | Purpose | Endpoint |
|---|---|---|
| Liveness | "Is the process alive?" | /health |
| Readiness | "Can it serve traffic?" | /ready |
| Startup | "Has it finished booting?" | /health |
Production config (in docker-compose.prod.yml):
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8001/health"]
interval: 30s
timeout: 5s
retries: 3What: On transient failures (network blips, 503s), retry the
request after an exponentially increasing delay.
Attempt 1 β fail β wait 0.5s
Attempt 2 β fail β wait 1.0s
Attempt 3 β fail β give up (or open circuit breaker)
Why: Most transient errors resolve within seconds. Retrying
immediately causes thundering herd problems; back-off spreads the load.
What: When migrating from a monolith, route traffic through the
gateway. Gradually replace monolith endpoints with microservices.
Gateway
βββ /api/users β NEW User Service
βββ /api/orders β NEW Order Service
βββ /api/legacy β OLD Monolith (shrinking)
Steps:
1. Deploy the gateway in front of the monolith.
2. Extract one bounded context at a time.
3. Route new traffic to the microservice; keep the monolith as fallback.
4. Remove the monolith route once fully migrated.
| Signal | Action |
|---|---|
| Two teams own the same codebase | Split |
| Module has independent deploy cycle | Split |
| Feature requires a different DB | Split |
| Two modules share the same table | Keep together |
| Feature is < 200 lines of code | Keep together |
*By Datanest Digital β Microservices Patterns Guide*
Get the full Python Microservices Kit and unlock everything.
Get the complete guide with every chapter unlocked, including code samples, diagrams, and best practices.
Access all interactive tools with complete data, all workload profiles, and the full scenario library.
Downloadable source code, configuration files, and working examples from every chapter.
Free updates for life. Every new chapter, tool, and improvement included.