Queopius

Modelos Eloquent demasiado grandes

El problema no es Eloquent. El problema aparece cuando el modelo se convierte en el lugar donde termina todo: queries, reglas de negocio, transformaciones, decisiones de producto, permisos y lógica de presentación.

Laravel Health Check5 min
Imagen destacada del artículo Modelos Eloquent demasiado grandes publicado en LinkedIn por Queopius
Laravel Health CheckEloquentDeuda técnicaArquitectura

El problema no es Eloquent, es la acumulación de responsabilidades

Eloquent es una de las piezas más productivas de Laravel. Permite modelar relaciones, consultar datos y expresar parte del comportamiento del dominio de forma muy cómoda. Esa comodidad es precisamente lo que hace que muchos proyectos empiecen a dejar cada nueva regla dentro del modelo.

Al principio parece una buena decisión: el código está cerca de los datos y todo avanza rápido. El problema llega cuando el modelo deja de representar una entidad y empieza a coordinar procesos completos. En ese momento, cambiar una regla comercial, una respuesta de API o una consulta crítica puede implicar tocar una clase que ya concentra demasiadas razones para cambiar.

Un modelo grande no es malo solo por tener muchas líneas. Es preocupante cuando mezcla persistencia, negocio, presentación, contexto HTTP y coordinación de efectos secundarios. Esa mezcla vuelve difuso el límite entre “qué es esta entidad” y “qué hace el sistema cuando ocurre algo con esta entidad”.

Señales de que un modelo está creciendo demasiado

Una primera señal es encontrar métodos largos que no describen comportamiento natural del modelo. Por ejemplo, métodos que preparan una respuesta completa para un endpoint, calculan decisiones comerciales con muchas condiciones o coordinan varios modelos externos. El nombre puede sonar inocente, pero el contenido ya no pertenece a la entidad.

También conviene mirar los scopes. Un scope pequeño y expresivo puede ser útil. Un conjunto de scopes que acumula filtros condicionales, dependencias de usuario, reglas de visibilidad, ordenación de producto y lógica de permisos empieza a comportarse como un Query Object escondido dentro del modelo.

Otras señales habituales son accessors y mutators que resuelven decisiones de producto, dependencia indirecta de Request, Auth, sesión o configuración, respuestas de API preparadas dentro del modelo, reglas de negocio mezcladas con persistencia y tests que solo pueden cubrirse creando demasiado contexto alrededor.

Por qué pasa tanto en Laravel

Laravel permite avanzar rápido y eso es una ventaja real. En una primera versión, poner lógica cerca de Eloquent puede reducir fricción y ayudar a validar producto. El problema no está en empezar simple, sino en no revisar los límites cuando el sistema cambia de escala.

Muchos proyectos pasan de CRUD sencillo a producto en producción sin una capa de aplicación clara. Cuando aparece una nueva regla, el modelo parece el sitio natural para dejarla porque ya tiene relaciones, atributos y acceso a la base de datos. Si esa decisión se repite durante meses, el modelo termina actuando como servicio, query layer, transformer y policy al mismo tiempo.

No es un fallo de Laravel. Es una consecuencia normal de crecer con presión de entrega y sin una revisión técnica periódica. Precisamente por eso conviene detectar el patrón antes de que el modelo se convierta en una pieza que nadie quiere tocar.

Qué riesgos genera

El riesgo más visible es la lentitud. Cada cambio obliga a entender más contexto del necesario porque la clase ya no tiene una responsabilidad clara. Un ajuste en una regla de negocio puede afectar a una consulta, una transformación de API o una operación asíncrona que vive en el mismo archivo.

También aparece miedo a tocar código. Cuando un modelo concentra demasiada lógica, el equipo deja de confiar en cambios pequeños. Los tests son más difíciles de escribir porque cada método arrastra persistencia, relaciones, factories y estados secundarios. Incluso optimizar queries se vuelve más complejo porque la consulta está mezclada con decisiones de negocio.

A medio plazo, este patrón genera acoplamiento entre modelo, API y reglas de producto. Reutilizar lógica en un comando, un job o un endpoint nuevo obliga a cargar más dependencias de las necesarias. La deuda técnica es silenciosa porque la aplicación puede seguir funcionando, pero cada iteración cuesta más.

Cómo empezar a dividir responsabilidades

El primer criterio es conservar en el modelo el comportamiento esencial de la entidad: relaciones, casts, atributos relevantes, pequeñas reglas propias del estado y métodos que realmente describen algo que el modelo “es” o “sabe”. Si el método coordina un proceso, probablemente pertenece a otra capa.

Los casos de uso pueden extraerse a Services o Actions. Las consultas complejas pueden vivir en Query Objects, scopes más pequeños o clases específicas. La validación HTTP debería estar en Form Requests. La transformación de salida encaja mejor en API Resources. La autorización debería delegarse en Policies o Gates. Los procesos pesados o diferidos suelen pertenecer a Jobs.

No hace falta crear una arquitectura enorme de golpe. Tiene más sentido empezar por las zonas con más cambio, más bugs o más riesgo. Una extracción bien elegida reduce fricción inmediatamente; una extracción por estética puede añadir complejidad sin mejorar la capacidad de evolución.

Ejemplo conceptual

Imagina un modelo Order que calcula estado comercial, prepara el payload de API, aplica filtros avanzados, comprueba permisos y dispara notificaciones. Nada de eso rompe necesariamente producción, pero convierte Order en una pieza con demasiadas responsabilidades.

Una separación más clara podría dejar Order con relaciones, atributos y comportamiento esencial. CreateOrderAction o OrderService gestionaría el caso de uso. OrderQuery resolvería filtros complejos. OrderResource transformaría la salida para la API. OrderPolicy decidiría permisos. Un Job gestionaría notificaciones o procesos pesados.

La mejora no es “tener más clases”. La mejora es que cada cambio futuro tiene un lugar más evidente. Si cambia la respuesta de API, no tocas el modelo. Si cambia la autorización, no tocas la query. Si cambia el proceso de creación, no mezclas esa decisión con presentación.

Checklist rápida

Antes de aceptar otro método dentro del modelo, conviene preguntar: ¿representa comportamiento propio de la entidad o coordina un caso de uso? ¿Depende de HTTP, Request, Auth, sesión o contexto externo? ¿Está preparando una respuesta para frontend o API? ¿Contiene una query compleja difícil de reutilizar?

También ayuda revisar si mezcla reglas de negocio con persistencia, si sería más claro como Action, Service, Query Object, Resource o Policy, y si puede probarse sin montar demasiado estado alrededor. Si la respuesta a varias preguntas es negativa, el modelo probablemente está absorbiendo responsabilidades que ya conviene separar.

Cierre

Un modelo grande no siempre rompe la aplicación, pero suele indicar que la arquitectura está perdiendo límites. La señal importante no es el número exacto de líneas, sino la cantidad de decisiones distintas que conviven en la misma clase.

Si tu Laravel funciona, pero cada cambio empieza a tocar demasiadas piezas, una Auditoría Backend Laravel puede ayudarte a detectar qué responsabilidades conviene separar primero y qué quick wins tienen más impacto real.

¿Tu Laravel muestra señales parecidas?

Si tu aplicación funciona, pero cada cambio empieza a ser más lento, más frágil o más difícil de explicar, una Auditoría Backend Laravel puede ayudarte a ordenar riesgos, quick wins y prioridades técnicas.