| name | database-patterns |
| description | Database design patterns including schema design, migrations, soft deletes, and Exposed ORM. Use when creating tables, writing migrations, or implementing repositories. |
| allowed_tools | ["Read","Write","Edit","Glob","Grep"] |
Database Patterns — Quick Reference
Hard Rules
| Rule | Do | Don't |
|---|
| Data types | TEXT | VARCHAR |
| Primary keys | UUID | SERIAL, BIGINT |
| Soft delete | deleted_at TIMESTAMP | DELETE FROM |
| Foreign keys | App-level validation | REFERENCES, ON DELETE CASCADE |
| Standard columns | id, created_at, updated_at, deleted_at | Skip any of these |
Migration Template
SQL migration with table, trigger, and partial indexes for soft delete.
See code-templates.md for the complete SQL template.
Exposed Table Object
Extend UUIDTable, use text() not varchar(), add standard timestamp columns.
See code-templates.md for the complete template.
Entity Data Class
Implement Entity<Instant>, include all business fields + createdAt, updatedAt, deletedAt.
See code-templates.md for the complete template.
Repository Pattern
Interface + Default* implementation. Reads on db.replica, writes on db.primary. Soft delete via deletedAt update. convert() method maps ResultRow to entity.
See code-templates.md for the full interface + implementation code.
When Creating a New Table
Full checklist:
- SQL migration file (next number in sequence)
- Table object in
module-repository/table/
- Entity data class in
module-repository/entity/
- Enum/constants in
module-repository/constant/ (if needed)
- Repository interface + implementation
- Factory bean for repository
- Repository tests
Flyway Rules
- NEVER add a migration that fills a gap in deployed sequence
- NEVER rename an already-deployed migration file
- Migration numbers must be sequential from the latest
- Keep migrations simple and focused (one table per migration)
Gotchas
- Only
id uses .value -- everything else is direct. row[Table.id].value gives UUID, but row[Table.projectId] already returns UUID. Adding .value to non-id columns causes compile errors.
gen_random_uuid() vs uuid_generate_v4() -- pick one per project. Mixing them works but confuses code review. Check existing migrations for which one the project uses.
- Don't re-declare
createdAt, updatedAt, deletedAt if extending SoftDeleteTable. They're inherited. Declaring them again causes duplicate column errors.
- Forgetting
WHERE deleted_at IS NULL on indexes wastes space. Every index on a soft-delete table should be partial. Full indexes include dead records nobody queries.
- Text columns that hold JSON should still use
text() in Exposed. JSONB in Postgres, text() in Kotlin, serialize/deserialize in the entity layer.