How to implement the Database per service pattern in Python
Simulate separate databases per microservice in Python using dataclasses and in-memory dictionaries, showing how services own their data independently.
Python code
61 linesimport json
from dataclasses import dataclass, asdict
from typing import Dict, List
@dataclass
class User:
id: int
name: str
email: str
@dataclass
class Order:
id: int
user_id: int
product: str
amount: float
class UserServiceDB:
"""Simulates a separate database for the User service."""
def __init__(self) -> None:
self._users: Dict[int, User] = {}
def create_user(self, user: User) -> User:
self._users[user.id] = user
return user
def get_user(self, user_id: int) -> User | None:
return self._users.get(user_id)
class OrderServiceDB:
"""Simulates a separate database for the Order service."""
def __init__(self) -> None:
self._orders: Dict[int, Order] = {}
def create_order(self, order: Order) -> Order:
self._orders[order.id] = order
return order
def get_orders_for_user(self, user_id: int) -> List[Order]:
return [o for o in self._orders.values() if o.user_id == user_id]
if __name__ == "__main__":
user_db = UserServiceDB()
order_db = OrderServiceDB()
user_db.create_user(User(id=1, name="Alice", email="alice@example.com"))
order_db.create_order(Order(id=101, user_id=1, product="Laptop", amount=1200.00))
order_db.create_order(Order(id=102, user_id=1, product="Mouse", amount=25.50))
user = user_db.get_user(1)
orders = order_db.get_orders_for_user(1)
print(json.dumps({
"user": asdict(user) if user else None,
"orders": [asdict(o) for o in orders]
}, indent=2))
Output
{
"user": {
"id": 1,
"name": "Alice",
"email": "alice@example.com"
},
"orders": [
{
"id": 101,
"user_id": 1,
"product": "Laptop",
"amount": 1200.0
},
{
"id": 102,
"user_id": 1,
"product": "Mouse",
"amount": 25.5
}
]
}
How it works
This code models the database per service pattern by giving each microservice its own dedicated data store — UserServiceDB and OrderServiceDB each hold only their relevant entities. Dataclasses provide a clean, typed structure for the domain models, while asdict converts them to plain dictionaries for easy JSON serialization. The services expose narrow, domain-specific APIs (e.g., get_orders_for_user) instead of generic database access, mirroring how real microservices enforce data ownership boundaries. In production you'd replace the in-memory dictionaries with actual databases like PostgreSQL or DynamoDB, but the service boundary and ownership concept stays identical.
Common mistakes
- Letting one service query another service's database directly, breaking the ownership boundary
- Returning raw database objects instead of serializable DTOs or dicts
- Forgetting that `User | None` requires Python 3.10+ for the union type syntax
- Copying entities between services instead of using API calls or events
Variations
- Use SQLite with separate `.db` files per service instead of in-memory dicts
- Add repository interfaces to abstract the storage backend for easier testing
Real-world use cases
- Designing a microservices architecture where a User service owns profiles and an Order service owns transactions independently.
- Prototyping service boundaries in a monorepo before splitting into separate deployable services.
- Writing unit tests that mock service databases without needing a real database connection.
Sponsored
More from Microservices patterns
- BFF aggregation pattern: combine multiple service responses in Python easy
- Backward Compatible Schema Evolution in Python medium
- Bulkhead Thread Pool per Service Mock in Python medium
- CQRS with Separate Read and Write Repositories in Python medium
- Cache-Aside Pattern in Python: Per-Service Mock easy
- Consumer Driven Contract Pact Mock in Python medium
Keep learning
Related tutorials and quizzes for this topic.