Repository pattern
El resto de la aplicación no debería saber ni necesitar saber si los datos vienen de Prisma, de SQL directo o de una API externa. Solo debería conocer una interfaz en términos del dominio:
export interface UserRepository { findActiveUsers(): Promise<User[]> findById(id: string): Promise<User | null> save(user: User): Promise<void>}findActiveUsers(), no SELECT * FROM users WHERE active = true. La interfaz habla el lenguaje del negocio, no el lenguaje de la base de datos.
Una implementación concreta
Sección titulada «Una implementación concreta»import type { PrismaClient } from "@prisma/client"import type { UserRepository } from "./user-repository"
export class PrismaUserRepository implements UserRepository { constructor(private readonly prisma: PrismaClient) {}
findActiveUsers() { return this.prisma.user.findMany({ where: { active: true } }) }
findById(id: string) { return this.prisma.user.findUnique({ where: { id } }) }
async save(user: User) { await this.prisma.user.upsert({ where: { id: user.id }, update: user, create: user }) }}El service que usa este repositorio solo conoce UserRepository, no PrismaUserRepository:
export class UserService { constructor(private readonly repo: UserRepository) {}
listActive() { return this.repo.findActiveUsers() }}Qué compra esto
Sección titulada «Qué compra esto»- Cambiar de ORM (Prisma → Drizzle, por ejemplo) significa escribir una nueva clase que implemente
UserRepository— cero cambios en services. - Testear sin base de datos real — un
InMemoryUserRepositoryque implemente la misma interfaz sirve para tests rápidos y deterministas.
Este patrón es, en términos de arquitectura hexagonal, un puerto (UserRepository) con sus adaptadores (PrismaUserRepository, InMemoryUserRepository) — ver Arquitectura hexagonal para el concepto general de puertos y adaptadores.
Cuándo NO vale la pena
Sección titulada «Cuándo NO vale la pena»En un proyecto chico donde nunca vas a cambiar de ORM ni necesitas testear sin DB, el repository es una capa de indirección que solo agrega archivos: una interfaz, una implementación, y el mismo código que hubieras escrito llamando a Prisma directo desde el service. Si esa flexibilidad nunca se va a usar, no está agregando valor real — solo ceremonial.