Skip to content

ADR 004: Repository Pattern for Data Access

Status

Accepted

Context

Folionaut needs to:

  • Access data from Turso/libSQL database
  • Support potential database migration in the future
  • Enable unit testing without database dependencies
  • Provide consistent data access patterns
  • Handle soft deletes and versioning

Requirements:

  • Type-safe queries with TypeScript
  • Abstraction over Drizzle ORM specifics
  • Support for complex queries (filtering, pagination)
  • Transaction support for multi-table operations

Decision

Implement the Repository Pattern to abstract data access, with Drizzle ORM as the underlying query builder.

typescript
interface Repository<T, CreateDTO, UpdateDTO> {
  findById(id: string): Promise<T | null>
  findMany(filter: FilterOptions): Promise<PaginatedResult<T>>
  create(data: CreateDTO): Promise<T>
  update(id: string, data: UpdateDTO): Promise<T>
  delete(id: string): Promise<void>
}

// Content-specific repository
interface ContentRepository extends Repository<Content, CreateContent, UpdateContent> {
  findBySlug(slug: string): Promise<Content | null>
  findByType(type: ContentType): Promise<Content[]>
  findPublished(): Promise<Content[]>
  getHistory(id: string): Promise<ContentVersion[]>
  restore(id: string, version: number): Promise<Content>
}

Alternatives Considered

OptionProsCons
Direct Drizzle callsSimple, no abstractionScattered queries, hard to test
Active Record patternORM-style, entities have methodsTight coupling, heavy objects
Query Builder serviceCentralized, composableNot as clean an interface
Repository PatternClean interface, testable, swappableMore code, potential over-abstraction
CQRSOptimized reads/writesOverkill for this scale

Consequences

Positive

  • Testability: Mock repositories in unit tests
  • Single Responsibility: Data access logic centralized
  • Type Safety: Strong typing at repository boundary
  • Flexibility: Can swap database implementations
  • Consistency: Standard patterns across all entities
  • Soft delete handling: Repositories automatically filter deleted records

Negative

  • More code: Repository interfaces + implementations
  • Indirection: One more layer to navigate
  • Learning curve: Team must understand the pattern

Mitigations

  • Keep it simple: Don't over-abstract, only what's needed
  • Base class: Share common CRUD logic
  • Clear naming: Repository methods describe business operations

References

Released under the MIT License.