
choosing-pattern
by juanre
Postgres database manager
SKILL.md
name: choosing-pattern description: Use when deciding which pgdbm pattern to use (standalone, dual-mode library, or shared pool) - provides decision tree based on deployment context without requiring doc exploration
Choosing the Right pgdbm Pattern
Overview
Core Principle: Choose pattern based on deployment context and reusability needs.
pgdbm supports three main patterns. This skill helps you choose the right one in <30 seconds without reading multiple docs.
Quick Decision Tree
What are you building?
│
├─ Reusable library/package for PyPI?
│ └─ → DUAL-MODE LIBRARY pattern
│ • Accept connection_string OR db_manager
│ • Works standalone AND embedded
│
├─ Single application with multiple services/modules?
│ └─ → SHARED POOL pattern
│ • ONE pool, many schema-isolated managers
│ • Most efficient for production
│
└─ Simple standalone service/microservice?
└─ → STANDALONE pattern
• AsyncDatabaseManager(DatabaseConfig(...))
• Simplest setup
Pattern Selection Table
| If you have... | Use this pattern | Key indicator |
|---|---|---|
| Library published to PyPI | Dual-Mode | Code needs to work in someone else's app |
| FastAPI monolith with routers | Shared Pool | Multiple services, same process |
| Multiple services, same app | Shared Pool | Need connection efficiency |
| Background worker (separate process) | Standalone | Different OS process |
| Simple microservice | Standalone | One service, own database |
| Multi-tenant SaaS | Shared Pool | Many tenants, schema isolation |
Detailed Decision Criteria
Use DUAL-MODE LIBRARY When:
Triggers:
- You're publishing to PyPI
- You're building internal shared library
- Code will be used by other developers
- Library might be used alongside other pgdbm libraries
Key characteristics:
- Unknown deployment context
- Must work standalone OR embedded
- Always runs own migrations
- Schema-agnostic via
{{tables.}}
Red flags you need this:
- You're writing
import mylibin your README - Someone else's app will import your code
- You don't control the database configuration
Example minimal setup:
class MyLibrary:
def __init__(
self,
connection_string: Optional[str] = None,
db_manager: Optional[AsyncDatabaseManager] = None,
):
if not connection_string and not db_manager:
raise ValueError("Provide one or the other")
self._external_db = db_manager is not None
self.db = db_manager
self._connection_string = connection_string
async def initialize(self):
if not self._external_db:
config = DatabaseConfig(connection_string=self._connection_string)
self.db = AsyncDatabaseManager(config)
await self.db.connect()
# ALWAYS run migrations
migrations = AsyncMigrationManager(
self.db, "migrations", module_name="mylib"
)
await migrations.apply_pending_migrations()
For complete implementation: See pgdbm:dual-mode-library skill
Use SHARED POOL When:
Triggers:
- Multiple services in same Python process
- FastAPI app with multiple routers
- Monolith with logical service separation
- Multi-tenant SaaS application
Key characteristics:
- Services share database connection pool
- Each service gets own schema
- Connection efficiency critical
- All services in same application
Red flags you need this:
- You're creating multiple
AsyncDatabaseManager(DatabaseConfig(...))in same app - You're hitting PostgreSQL connection limits
- You have >2 routers/services needing database
Example minimal setup:
# In lifespan
config = DatabaseConfig(connection_string="postgresql://...")
shared_pool = await AsyncDatabaseManager.create_shared_pool(config)
# Each service gets schema-isolated manager
users_db = AsyncDatabaseManager(pool=shared_pool, schema="users")
orders_db = AsyncDatabaseManager(pool=shared_pool, schema="orders")
# Run migrations for each
for db, path, name in [(users_db, "migrations/users", "users"), ...]:
migrations = AsyncMigrationManager(db, path, name)
await migrations.apply_pending_migrations()
For complete implementation: See pgdbm:shared-pool-pattern skill
Use STANDALONE When:
Triggers:
- Single service, dedicated database
- Background worker (separate process)
- Simple microservice
- Development/testing
- Service can't share connections
Key characteristics:
- Creates own connection pool
- Controls full database lifecycle
- Simplest pattern
- Most straightforward
Red flags you need this:
- Separate OS process (can't share pool anyway)
- Only one logical service
- Development environment
- Learning pgdbm
Example minimal setup:
config = DatabaseConfig(connection_string="postgresql://...")
db = AsyncDatabaseManager(config)
await db.connect()
migrations = AsyncMigrationManager(db, "migrations", module_name="myservice")
await migrations.apply_pending_migrations()
# Use db
user_id = await db.fetch_value(
"INSERT INTO {{tables.users}} (email) VALUES ($1) RETURNING id",
email
)
await db.disconnect()
For complete implementation: See pgdbm:standalone-service skill
Wrong Pattern Red Flags
🚫 You Chose WRONG Pattern If:
Using Standalone but should use Shared Pool:
- You create multiple
AsyncDatabaseManager(DatabaseConfig(...))to same database - You see warning: "Creating another connection pool to..."
- You're hitting PostgreSQL
max_connectionslimit - All services run in same Python process
Using Shared Pool but should use Dual-Mode:
- Your code will be imported by other apps
- You're publishing to PyPI
- Users need to provide their own database
Using Dual-Mode but should use Standalone:
- You control entire deployment
- Code never used as library
- Adding unnecessary complexity
Common Ambiguous Cases
"Background worker, same database as main app"
Question to ask: Same process or different process?
- Same process (threads/asyncio tasks): Use Shared Pool
- Different process (separate Python process): Use Standalone
- Each process creates own pool (can't share across processes)
- Use schema isolation to prevent table conflicts
- Worker uses schema="worker", main uses schema="main"
"Multiple microservices, containerized"
Answer: Each container = Standalone pattern
- Containers are separate processes
- Can't share pools across containers
- Use schema isolation if sharing same database
- Each service:
AsyncDatabaseManager(DatabaseConfig(...))
"Library used internally, not published"
Answer: Still use Dual-Mode if used by multiple apps
- "Internal" doesn't mean "standalone"
- If imported by different projects, use Dual-Mode
- If only used in one app, can use Shared Pool
Pattern Comparison
| Aspect | Standalone | Dual-Mode | Shared Pool |
|---|---|---|---|
| Complexity | Low | Medium | Medium |
| Flexibility | Low | High | Medium |
| Connection Efficiency | Low | Varies | High |
| Use Case | Simple services | Reusable libraries | Multi-service apps |
| Pool Creation | Creates own | Conditional | Uses provided |
| Migration Management | Owns | Always runs own | Each service runs own |
| Best For | Microservices, workers | PyPI packages | Monoliths, multi-tenant |
Decision Process Example
Scenario: "I'm building a FastAPI app with user authentication, blog posts, and comments"
Decision process:
- Multiple services? Yes (auth, blog, comments)
- Same Python process? Yes (all in FastAPI app)
- Will be reused as library? No (application code)
Answer: Shared Pool Pattern
Why:
- Multiple services = need isolation
- Same process = can share pool
- Not reusable = don't need dual-mode flexibility
Quick Self-Check
Before implementing, ask:
-
Who creates the database manager?
- Me = Standalone or Shared Pool
- Could be me or someone else = Dual-Mode
-
How many services need database access?
- 1 = Standalone (probably)
- 2+ in same process = Shared Pool
- 2+ in different processes = Standalone each
-
Will my code be imported by other projects?
- Yes = Dual-Mode
- No = Standalone or Shared Pool
Next Steps
Once you've chosen:
- Dual-Mode: See
pgdbm:dual-mode-libraryfor full implementation - Shared Pool: See
pgdbm:shared-pool-patternfor full implementation - Standalone: See
pgdbm:standalone-servicefor full implementation
All patterns use:
{{tables.}}syntax (mandatory)- Unique
module_namein migrations (mandatory) - Schema isolation for multi-service (recommended)
The Iron Rule
Whatever pattern you choose, NEVER:
- Create multiple pools to same database in same process
- Hardcode schema names in SQL
- Skip
module_namein AsyncMigrationManager - Switch
db.schemaat runtime
These violations break pgdbm's core assumptions.
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon