Hay una fase en la construcción de un producto que casi nadie documenta, pero que todos sufrimos: la capa donde HTTP se encuentra con el Dominio.
Ese punto exacto en el que:
-
Llegan las peticiones del usuario.
-
El sistema tiene que decidir qué hacer con ellas.
-
Y hay que devolver una respuesta sin romper nada por el camino.
Y aquí es terriblemente fácil equivocarse.
Porque cuando empiezas a construir un proyecto en Laravel (o cualquier framework MVC), la tentación es fuerte y tienes a crear controladores enormes, a meter la lógica en ellos “provisionalmente”, devolver datos sin pensar demasiado en la estructura y a copiar y pegar comportamiento entre rutas.
Parece rápido y parece práctico…. Hasta que un día abres un controlador con 600 líneas te preguntas quién ha hecho ese desastre. Y la respuesta duele: fuiste tú, hace tres semanas.
Si has seguido las entregas anteriores de KPIAds, sabrás que ya tenemos:
-
Qué hace el usuario (casos de uso).
-
Qué dejamos en la base de datos (entidades y relaciones).
-
Cómo piensa el negocio (bounded contexts).
Ahora toca responder a lo tangible:
👉 ¿Dónde vive cada parte de la lógica?
👉 ¿Qué ocurre exactamente cuando un usuario hace click?
👉 ¿Qué archivos toman decisiones y cuáles no?
En otras palabras: pasamos del QUÉ del dominio al CÓMO del código.
El anti-patrón del controlador “Dios”
Si vienes de tutoriales básicos, es normal que caigas en esto:
-
Recibes la request en el controlador.
-
Validar los datos ahí mismo.
-
Llamas a 3 servicios distintos o haces las queries ahí mismo.
-
Haces cálculos de negocio ahí mismo.
-
Renderizas la vista.
Eso funciona al principio… y se convierte en un infierno después ya que los controladores enormes tienen tres problemas letales:
-
Mezclan responsabilidades (saben demasiado).
-
Son imposibles de testear (tienes que simular todo el proceso HTTP para probar una suma).
-
Duplican lógica por todas partes.
Y hay un síntoma universal: cada nueva funcionalidad hace el código más frágil.
La solución: Action + Invokable Controllers
En KPIAds he tomado una decisión de arquitectura clara desde el principio y es que cada endpoint HTTP se implementa con un controlador invocable.
Es decir, cada endpoint se implementa con un solo método llamado __invoke.(
¿Por qué? Porque de esta forma toda la lógica de negocio vive en una Action.
Esto te obliga a tener controladores pequeños, encapsular la lógica en clases reutilizables y dar a cada caso de uso su propio espacio.
Por ejemplo, tendremos ShoProjectMetricsController el cual delega en GetProjectMetricsAction.
La primera se dedica a HTTP y la segunda se dedica al Dominio.
De esta forma toda petición en KPIAds sigue el mismo camino sagrado:
➡️ El usuario hace una petición
➡️ Route encuentra el controlador invocable
➡️ Controller valida y delega
➡️ Action ejecuta el caso de uso
➡️ Controller decide formato de respuesta
➡️ View (Blade) presenta los datos
O resumido así: Route → Controller → Action → View
La clave es que cada paso hace solo una cosa.
Si queremos llevar esto a un ejemplo real donde, por ejemplo, el usuario entre a un dashboard de métricas y quiera ver ingresos, gasto, ROAS y CAC el proceso sería:
-
La ruta dice dónde vive ese endpoint.
-
El controlador recibe la request y valida que tengas permisos.
-
La action calcula, consulta al repositorio y empaqueta los datos.
-
La vista lo presenta.
Además de ser un proceso mucho más lógico y ordenado, este patrón tiene unas ventajas que cualquier desarrollador debería tener en cuenta como:
-
Puedes testear la Action de forma aislada con test unitarios.
-
Puedes llamar a esa misma Action desde un job, un comando de consola o una API externa (importante si queremos escalar el proyecto a futuros).
-
Y no necesitas abrir 8 archivos para entender qué pasa en el proceso.
¿Por qué merece la pena esta forma de trabajar?
Porque te prepara para el futuro sin complicarte el presente.
Hoy KPIAds es un monolito pero gracias a este diseño, mañana podría:
-
Mover Actions pesadas a colas (Jobs).
-
Exponer endpoints API sin tocar controladores.
-
Migrar acciones a microservicios.
-
Reemplazar Blade por Inertia o Livewire.
Y nada de eso rompe la idea central: una petición HTTP no es un caso de uso, solo es el disparador.
Con esta entrega quería, además de compartirte el progreso de KPIAds (que llevaba un par de semanas sin hacerlo) dejarte claro que trabajar con un monolito no significa que vivamos en un caos.
El desorden lo creas tú.
Cuando decides conscientemente dónde vive la lógica, cómo viajan los datos y qué responsabilidades tiene cada capa, cada nueva funcionalidad se vuelve una extensión natural, no una amenaza.
Con esto completo, KPIAds ya está preparado para la siguiente estación.
Nos vemos en la próxima entrega
Julián
Publicado originalmente en CERO a SENIOR.
