How to Demonstrate the Shared Database Antipattern in Python
This code simulates a shared database where multiple services write and read the same SQLite table, illustrating tight coupling and its pitfalls.
Python code
46 linesimport sqlite3
from pathlib import Path
def create_shared_db(db_path: Path) -> None:
"""Mock demonstrating the shared database antipattern where multiple
services access the same database, causing tight coupling."""
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
service TEXT NOT NULL
)
""")
conn.commit()
conn.close()
def add_user(db_path: Path, name: str, service: str) -> None:
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute("INSERT INTO users (name, service) VALUES (?, ?)", (name, service))
conn.commit()
conn.close()
def list_users(db_path: Path) -> list:
conn = sqlite3.connect(db_path)
cur = conn.cursor()
rows = cur.execute("SELECT id, name, service FROM users ORDER BY id").fetchall()
conn.close()
return [{"id": r[0], "name": r[1], "service": r[2]} for r in rows]
if __name__ == "__main__":
db = Path("mock_shared.sqlite3")
if db.exists():
db.unlink()
create_shared_db(db)
add_user(db, "Alice", "auth-service")
add_user(db, "Bob", "billing-service")
add_user(db, "Carol", "auth-service")
for user in list_users(db):
print(f"ID={user['id']}, Name={user['name']}, Service={user['service']}")
db.unlink()
Output
ID=1, Name=Alice, Service=auth-service
ID=2, Name=Bob, Service=billing-service
ID=3, Name=Carol, Service=auth-service
How it works
This mock uses a single SQLite database to represent a shared schema that multiple services access. Each add_user call inserts a row tagged with a service name, and list_users reads the entire table. The lack of per-service tables or isolation demonstrates how a change in one service's schema can break others. The CREATE TABLE IF NOT EXISTS ensures the schema is defined on every startup, but this still couples all services to one technical contract.
Common mistakes
- Assuming SQLite handles concurrent writes safely without connection timeouts
- Forgetting to close connections, leading to locked database files
- Sharing a database for unrelated concerns instead of using API boundaries
- Ignoring migration conflicts when multiple services evolve the schema
Variations
- Use a real client-server database like PostgreSQL to emulate production scale.
- Refactor to have each service own its schema and expose APIs for data access.
Real-world use cases
- Designing multi-service systems where teams mistakenly share a central database for convenience.
- Teaching microservices architecture by contrasting shared databases with service-owned data stores.
- Reviewing legacy codebases to identify hidden coupling risks before splitting monoliths.
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.