kbwen / database-design
Design schemas and migrations when persistent data structures change.
0
SKILL.md4.9 KB
<!-- This is a SCAFFOLD skill -->
Database Design
When to Apply
- Classification: feature (if new tables/columns), architecture-change (schema redesign), hotfix (data-related bug)
- Phase: /plan (schema design), /implement (migration creation), /review (schema review), /test (data integrity)
- Trigger: Task involves creating tables, modifying schema, writing migrations, or changing data access patterns
Conventions
Customize after /app-init: Replace these generic conventions with your project's ADR and ORM-specific patterns.
Table Design
- Every table MUST have:
id(primary key),created_at,updated_at - Use UUIDs or auto-increment IDs consistently (per ADR decision)
- Soft delete: add
deleted_atcolumn if ADR specifies soft delete - Table names: plural, snakecase (e.g.,
users, `orderitems`) - Column names: snakecase (e.g., `firstname
,is_active`)
Relationship Patterns
| Relationship | Implementation | Example |
|---|---|---|
| One-to-Many | Foreign key on "many" side | orders.user_id → users.id |
| Many-to-Many | Junction table | user_roles (user_id, role_id) |
| One-to-One | Foreign key + unique constraint | user_profiles.user_id (UNIQUE) → users.id |
| Self-referencing | Foreign key to same table | categories.parent_id → categories.id |
Index Strategy
- Every foreign key column MUST have an index
- Columns used in WHERE clauses frequently → add index
- Composite indexes: put the most selective column first
- Unique constraints for business uniqueness rules (email, username, etc.)
- DO NOT over-index: every index slows writes
Migration Rules
- One migration per logical change (don't mix table creation with data backfill)
- Migrations MUST be reversible: every
upmust have a correspondingdown - Never modify a shipped migration: create a new migration instead
- Data migrations separate from schema migrations: schema first, data second
- Test rollback: after writing migration, verify
downworks cleanly
Query Patterns
- Use parameterized queries / ORM bindings — NEVER string concatenation for SQL
- Limit SELECT columns:
SELECT id, namenotSELECT * - Add
LIMITto all list queries (even if paginated — belt and suspenders) - Use transactions for multi-table writes
- Batch inserts for bulk operations (not one INSERT per row)
Data Integrity
- Foreign key constraints enforced at DB level (not just application level)
- NOT NULL on required fields (don't rely on application validation alone)
- CHECK constraints for enum-like fields or value ranges
- Default values for fields that have sensible defaults
Checklist
During /plan:
- [ ] Schema changes documented in spec (new tables, modified columns)
- [ ] Indexes planned for query patterns
- [ ] Migration rollback strategy defined
- [ ] Data backfill plan (if modifying existing tables with data)
During /implement:
- [ ] Migration file created (not manual SQL against DB)
- [ ] Foreign keys have indexes
- [ ] NOT NULL and DEFAULT constraints set appropriately
- [ ] Migration tested: up → verify → down → verify → up
- [ ] No raw SQL string concatenation (use parameterized queries)
During /review:
- [ ] No N+1 queries (use eager loading / JOIN where appropriate)
- [ ] Transaction boundaries correct (all-or-nothing for related writes)
- [ ] No schema changes without migration file
- [ ] Backward compatibility: old code can still function until deployed together
- [ ] Sensitive data columns identified (for encryption/hashing per security guardrails)
Heading-Scoped Read Note
For phase-entry loading, read only:
When to ApplyChecklist
Load Conventions, Anti-Patterns, and References on full read or cache miss only.
Anti-Patterns
- Schema via ORM only: Relying on ORM auto-sync without migration files. No rollback, no history, no review.
- God table: One table with 30+ columns. Normalize or split by domain.
- Missing indexes on FK: Every foreign key needs an index. Databases don't add them automatically (except some).
- String-typed everything: Using VARCHAR for dates, numbers, booleans. Use proper types.
- No migration rollback: Writing
upwithoutdown. You will need to rollback someday. - Mixing DDL and DML: Schema changes and data backfill in the same migration. Split them.
- SELECT * in production code: Fetching all columns when you need two. Wastes memory and bandwidth.
- Trusting application-only validation: No DB constraints means bad data WILL get in eventually.
References
- Project ADR:
docs/adr/ADR-00N-project-architecture.md§ Database Design - Security guardrails:
.agent/rules/security_guardrails.md(A03: SQL Injection, A02: Secrets in connection strings) - Spec template:
.agentcortex/templates/spec-app-feature.md§ Database Schema