Apply Backend Database Patterns
Purpose
Provide practical patterns for safe data access and persistence: transactional workflows, consistent querying, and predictable performance.
When to use
Use this skill when you are:
- Implementing repository methods or complex queries
- Adding transactional “all-or-nothing” workflows
- Debugging performance issues (slow queries, N+1)
- Introducing pagination, filtering, or sorting
- Designing idempotent writes for retries
- Implementing soft delete or archival behavior
Inputs
- Data model (entities/relations) and invariants
- Expected read/write volume and latency requirements
- Failure modes that must be handled (conflicts, duplicates, timeouts)
- The query layer used by the codebase (ORM, query builder, raw SQL)
Outputs
- Repository method designs (inputs/outputs and invariants)
- A transaction strategy (where transactions start/end)
- Query patterns for pagination/filtering
- A verification checklist for correctness and performance
Core rules
- Persistence code MUST be isolated behind repositories (or a clearly defined data-access layer).
- Transactions MUST be used when partial failure would corrupt state.
- Writes SHOULD be idempotent when requests can be retried.
- Queries MUST be scoped and paginated for potentially large datasets.
Common patterns
Transactions
Use a transaction when:
- multiple writes must commit together
- you need consistency across reads and writes
- you need to prevent partial side effects
Avoid long-lived transactions:
- keep them as short as possible
- do not call external services inside a transaction unless the design explicitly accounts for it
Pagination
- Use cursor pagination when:
- datasets are large
- stable ordering is required
- Use offset pagination when:
- datasets are small
- simplicity matters more than absolute correctness under concurrent writes
Soft delete
Soft delete is appropriate when:
- you need auditability or recovery
- external systems may reference records
When using soft delete:
- queries MUST consistently filter out deleted rows unless explicitly requested
- unique constraints MAY need to include “deleted” flags
Avoid N+1 queries
- Fetch related entities via joins/includes when appropriate.
- Consider batching, caching, or explicit query composition.
Steps
- Define data invariants (what MUST always be true).
- Define repository contract:
- inputs
- outputs
- error cases
- Choose transaction boundaries for multi-step workflows.
- Implement queries with:
- explicit filters
- explicit ordering
- pagination for large lists
- Validate performance assumptions:
- expected query count
- indexes required
- hot paths
- Add verification:
- one correctness test
- one performance sanity check (where practical)
Verification
- Repository methods return expected results for valid inputs
- Transactions roll back completely on failure (no partial commits)
- Pagination returns correct page sizes and cursor/offset handling
- Soft delete queries correctly exclude deleted records by default
- Query performance meets latency targets (check query plans if needed)
- N+1 queries are not introduced (verify query count in tests)
Boundaries
- MUST NOT call external services inside database transactions
- MUST NOT expose ORM-specific types to service layer (unless intentionally standardized)
- MUST NOT skip transaction boundaries for multi-write operations
- MUST NOT use unbounded queries on large datasets
- SHOULD NOT use offset pagination for large or frequently-updated datasets
- SHOULD NOT hold transactions open longer than necessary
Included assets
- Templates:
./templates/includes pagination and transaction scaffolds. - Examples:
./examples/includes transactional workflow patterns.