patterntypescriptModerate
Repository Pattern: Abstract Data Access Behind a Domain Interface
Viewed 0 times
repository patterndata accessdomain abstractiondependency inversionin memory repositorytest doubles
Problem
Business logic directly calls ORM or database APIs. Switching databases, adding caching, or unit testing without a real database requires invasive changes throughout service code.
Solution
Define a repository interface in the domain layer. Implement it in the infrastructure layer. Inject the interface into services so the implementation can be swapped.
// Domain layer — no import of any DB library
interface UserRepository {
findById(id: string): Promise<User | null>;
findByEmail(email: string): Promise<User | null>;
save(user: User): Promise<void>;
delete(id: string): Promise<void>;
}
// Infrastructure layer
class PrismaUserRepository implements UserRepository {
constructor(private prisma: PrismaClient) {}
findById(id: string) { return this.prisma.user.findUnique({ where: { id } }); }
findByEmail(email: string) { return this.prisma.user.findUnique({ where: { email } }); }
async save(user: User) { await this.prisma.user.upsert({ where: { id: user.id }, create: user, update: user }); }
async delete(id: string) { await this.prisma.user.delete({ where: { id } }); }
}
// Test double — no database needed
class InMemoryUserRepository implements UserRepository {
private store = new Map<string, User>();
async findById(id: string) { return this.store.get(id) ?? null; }
async findByEmail(email: string) { return [...this.store.values()].find(u => u.email === email) ?? null; }
async save(user: User) { this.store.set(user.id, user); }
async delete(id: string) { this.store.delete(id); }
}Why
The repository interface is a seam: it lets the domain layer be tested with fast in-memory stores and lets the infrastructure be replaced (Postgres to MongoDB) without touching business logic.
Gotchas
- Avoid leaking query language into the interface (e.g.,
findAll(prismaWhere: Prisma.UserWhereInput)) — that defeats the abstraction. - Repositories are per aggregate root in DDD, not per database table. A UserRepository may touch multiple tables.
- Generic repositories (
Repository<T>) sound convenient but often fail to express domain-specific query needs cleanly.
Revisions (0)
No revisions yet.