Separar responsabilidades
Una aplicación de backend mantenible separa el código en capas, cada una con una única responsabilidad. La petición las atraviesa de fuera hacia dentro:
HTTP → Controlador → Servicio → Repositorio → Base de datos
Controlador (HTTP)
Es el handler (req, res). Su único trabajo es traducir HTTP: leer
req.body/req.params, llamar al servicio y responder con el estado y el
JSON adecuados. No contiene reglas de negocio ni SQL.
async function crear(req, res) {
const usuario = await servicio.registrar(req.body);
res.status(201).json(usuario);
}
Servicio (lógica de negocio)
Contiene las reglas: validaciones de dominio, "no permitir emails
duplicados", calcular totales, orquestar varios repositorios. No sabe de
req/res ni de SQL; pide datos al repositorio.
async function registrar(datos) {
const existe = await repo.buscarPorEmail(datos.email);
if (existe) throw new Error("Email duplicado");
return repo.crear(datos);
}
Repositorio (datos)
El que viste antes: solo acceso a datos.
Inyección de dependencias
Fíjate en que el servicio usa repo y el controlador usa servicio. En vez de
crear esas dependencias dentro (const repo = new RepoSQL()), se reciben
desde fuera. Eso es inyección de dependencias:
function crearServicio(repo) { // recibe el repo como parámetro
return {
async registrar(datos) {
if (await repo.buscarPorEmail(datos.email)) {
throw new Error("Email duplicado");
}
return repo.crear(datos);
},
};
}
Ventajas: en producción inyectas el repositorio real; en las pruebas inyectas un mock o un repo en memoria, sin tocar el servicio. Cada capa se prueba aislada, y el conjunto queda desacoplado y fácil de cambiar.