ecosystem-wikicómo extender los templates sin romper sus convenciones
‹ todas las recipes

¿Repositorio o port?

template-api
último cambio 2026-09-25

Las dos cosas son interfaces que alguien implementa fuera. Se parecen tanto que es fácil poner una donde va la otra, y viven en carpetas distintas a propósito.

Repositorio Port
Dónde vive domain/<agregado>/x.repository.ts application/ports/x.port.ts
Qué devuelve Entidades de dominio Primitivas, objetos planos, o nada
Qué modela Cómo se guarda algo tuyo Una capacidad técnica o un sistema ajeno
Cuántos hay 15 en petid-api 15 en petid-api

La regla

Mira la firma. Si menciona un tipo que no es entidad tuya, es un port.

// Repositorio: devuelve PetTag, que es tuyo → domain/
findByPublicCode(publicCode: string): Promise<PetTag | null>;

// Port: devuelve un Readable de Node, que no es tuyo → application/ports/
download(key: string): Promise<Readable>;

El caso que más confunde es un gateway que devuelve datos, como el de logs de notificaciones. Parece un repositorio: tiene consultas, devuelve filas, hasta pagina. Pero lo que devuelve es una interface NotificationLog declarada en el propio fichero del port — un objeto plano, no una entidad con comportamiento. Esos datos son de otro servicio; tú solo los lees de paso.

Las tres familias de port

Los 15 de petid-api caen aquí:

Clock merece un comentario aparte: envolver new Date() en un port parece exagerado hasta que tienes que testear un enfriamiento de 10 minutos. Con el port, el test pasa la hora que quiera.

Lo que hace que el patrón valga la pena

Cada port tiene dos implementaciones: la real y una degradada.

infrastructure/storage/    minio-file-storage.ts    +  unavailable-file-storage.ts
infrastructure/push/       notification-service-*   +  console-push-sender.ts
infrastructure/email/      email-service-sender.ts  +  console-email-sender.ts
infrastructure/notifications/  notification-service-*-gateway.ts  +  unavailable-*-gateway.ts

El módulo elige según configuración. Por eso la api arranca sin MinIO y sin notification-api: no revienta en el arranque, falla en el primer uso real y con un mensaje claro. Si añades un port, añade también su versión degradada — es la mitad del valor.

Cómo se escribe un port

Interfaz y Symbol en el mismo fichero, igual que un repositorio:

// application/ports/file-storage.port.ts
export interface UploadInput {
  key: string;
  body: Buffer;
  mimeType: string;
}

export interface FileStorage {
  upload(input: UploadInput): Promise<void>;
  delete(key: string): Promise<void>;
  download(key: string): Promise<Readable>;
}

export const FILE_STORAGE = Symbol("FileStorage");

Los tipos auxiliares que necesite (UploadInput) van en el propio fichero del port, no en domain/. No son del negocio, son de la frontera.

Trampas