Python Microservices Architecture — Core Concepts
What Microservices Architecture Means
Microservices architecture decomposes an application into small, independently deployable services, each owning a specific business capability. In Python, this typically means separate FastAPI, Flask, or gRPC applications that communicate over HTTP, message queues, or event streams.
The key principle isn’t “small code” — it’s independent deployment and ownership. A service should be deployable without coordinating with other teams.
Service Boundaries
The hardest part of microservices isn’t the technology — it’s deciding where to draw the lines. Good boundaries follow business domains, not technical layers.
Wrong way (technical split):
- Database service
- API service
- Background worker service
Right way (domain split):
- Order service (handles order creation, status, history)
- Inventory service (tracks stock, reservations)
- Notification service (emails, SMS, push)
Each service owns its data. The order service has its own database. The inventory service has its own. They never share databases directly — that’s the single most important rule of microservices.
Communication Patterns
Services need to talk to each other. Two fundamental styles:
Synchronous (Request/Response)
One service calls another and waits for a response. Typically REST or gRPC.
# Order service calls inventory service
import httpx
async def check_stock(product_id: str) -> bool:
async with httpx.AsyncClient() as client:
response = await client.get(
f"http://inventory-service/api/products/{product_id}/stock"
)
return response.json()["in_stock"]
Pros: Simple mental model, immediate feedback. Cons: Creates coupling — if the inventory service is down, the order service can’t function.
Asynchronous (Event-Driven)
Services publish events; other services react. Uses message brokers like RabbitMQ, Kafka, or Redis Streams.
# Order service publishes event
await broker.publish("order.created", {
"order_id": "abc-123",
"product_id": "prod-456",
"quantity": 2
})
# Inventory service subscribes and reacts
@broker.subscribe("order.created")
async def reserve_stock(event):
await inventory.reserve(event["product_id"], event["quantity"])
Pros: Loose coupling, services stay independent. Cons: Eventual consistency, harder to debug.
Most real systems use both: synchronous for queries that need immediate answers, asynchronous for commands and side effects.
Python Frameworks for Microservices
| Framework | Best For | Why |
|---|---|---|
| FastAPI | REST microservices | Async support, automatic OpenAPI docs, Pydantic validation |
| gRPC (grpcio) | High-performance inter-service calls | Binary protocol, code generation, streaming |
| Nameko | RPC-style microservices | Built-in service mesh, dependency injection |
| Flask | Simple HTTP services | Lightweight, huge ecosystem |
FastAPI has become the default choice for Python microservices because it combines async performance with developer experience (auto-docs, type checking, dependency injection).
Service Discovery and Routing
In a monolith, calling a function is a local function call. In microservices, you need to find the service first.
DNS-based: In Kubernetes, services get DNS names automatically (inventory-service.default.svc.cluster.local). This is the most common approach.
Service registry: Tools like Consul maintain a registry of service instances. Services register on startup and deregister on shutdown.
API Gateway: A single entry point (like Kong, AWS API Gateway, or a custom FastAPI app) routes external requests to the appropriate internal service.
Data Ownership
Each service owns its data store. This means:
- No shared databases between services
- Data duplication is expected and acceptable
- Services synchronize through events, not database joins
- Cross-service queries require API calls or data aggregation services
This feels wasteful at first, but it’s what enables independent deployment. If two services share a database, changing the schema requires coordinating both deployments — you’ve lost the main benefit.
Common Misconception
“Microservices are always better than monoliths.”
Microservices add significant operational complexity: distributed tracing, service-to-service authentication, network failures, data consistency across services. For teams smaller than 20-30 engineers, a well-structured monolith (sometimes called a “modular monolith”) is usually the better choice. Companies like Shopify and Basecamp run massive monoliths successfully.
Start monolithic. Split into microservices when you hit concrete scaling or team-independence problems, not because it sounds modern.
The one thing to remember: Microservices trade code simplicity for team independence — each service owns its data and deployment, but success depends on drawing the right boundaries and choosing the right communication patterns.
See Also
- Python Aggregate Pattern Why grouping related objects under a single gatekeeper prevents data chaos in your Python application.
- Python Bounded Contexts Why the same word means different things in different parts of your code — and why that is perfectly fine.
- Python Bulkhead Pattern Why smart Python apps put walls between their parts — like a ship that stays afloat even with a hole in the hull.
- Python Circuit Breaker Pattern How a circuit breaker saves your app from crashing — explained with a home electrical fuse analogy.
- Python Clean Architecture Why your Python app should look like an onion — and how that saves you from painful rewrites.