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.

Easy Python 3.9+ Aug 9, 2026 Microservices patterns 13 views 0 copies

Python code

46 lines
Python 3.9+
import 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

stdout
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

  1. Use a real client-server database like PostgreSQL to emulate production scale.
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Microservices patterns

Related tutorials and quizzes for this topic.