En el segundo vídeo de la serie de Refactorización que estoy haciendo en el canal de YouTube (aquí les dejo el enlace del canal http://bit.ly/32X70TM), vimos como podemos aplicar el Patrón Repository sobre Eloquent para aislar el código que se comunica con la base de datos y, de esta forma, hacer que nuestros test se ejecuten rápidamente.
Pero no es la única manera de separar el código de Eloquent. Podemos utilizar otra forma que me parece más sencilla y le da un mejor sentido a nuestro código.
Ocultando las consultas Eloquent
Supongamos que tenemos una gran consulta como la de la imagen:
Es muy común ver este tipo de consultas, donde establecemos el modelo y empezamos a encadenar y encadenar métodos como where()
, join()
, get()
, save(),
update()
, etc, etc. Y lo peor de todo, es que esta en un controlador!!
A simple vista parece una consulta bastante compleja y si le damos este código a un programador que recién entra a la empresa, seguramente va a sufrir un rato 😩.
Claramente, este es un «smell code» porque cumple con cada una de las características de un smell code: poco intuitivo, no re utilizable, difícil de testear y, a simple vista, no sabemos que es lo que queremos hacer con ese código.
Para no dejarlos con la intriga de que hace el código anterior, se los digo, simplemente esta buscando todas las personas que asisten a un evento, filtrando por un evento en particular y por el nombre y apellido de la persona. Como que es bastante código para algo tan simple, no?
Entonces, ¿cómo deberíamos utilizar Eloquent?
La forma ideal que deberíamos seguir, cuando hacemos consultas con Eloquent, es ocultar dicha consulta en el mismo modelo. Por ejemplo, para el caso anterior enviaríamos toda esa consulta al modelo, de la siguiente forma:
Y nuestro controlador queda mucho más delgado:
Como pueden ver, ahora inyectamos el modelo en el constructor del controlador (más adelante veremos porque) y luego llamamos al nuevo método. ¿Ahora no queda mucho más claro que es lo que hace aquella enorme consulta?
Además, al ser una función del modelo la podríamos utilizar en cualquier parte de nuestro código y no solo en ese controlador 😉🤙.
¿Y no podríamos utilizar los scope de Laravel para esto?
Si, tranquilamente podríamos utilizar los scope de Laravel para esto. Pero el enfoque no sería reemplazar el método byEventAndFullname()
por scopeByEventAndFullname()
ya que el sentido de los scopes es hacer pequeñas partes de consultas para encadenarlas en una consulta mas compleja. Así que, lo que yo haría seria fusionar estas dos técnicas de la siguiente forma:
¿Y que hay con los tests?
Si, también se puede testear ✊:
Utilizamos Mock para poder «falsear» el modelo que inyectamos en el constructor del controlador en el paso anterior. Ahora quedo totalmente testeable.
Conclusión
Sin la necesidad de utilizar repositorios vimos como eliminar un smell code utilizando la clase del modelo para aplicar la técnica de refactorización «Extracción a clase». De esta forma hicimos nuestro código re utilizable, testeable y fácil de entender. Espero que les haya gustado y nos vemos en la próxima.
Hola Matias, gracias muy buen post, un detalle interesante es la «eloquencia» en los métodos del modelo para los scope, darle legibilidad permite no solo un mejor código si no mas descriptivo para que cualquier developer tenga una idea clara de lo que hace cierto código, excelente, yo trato de tener un standard, por ser eloquent tan facil intento mantener el llamado a eloquent en un controlador corto, y usar el patron repository solo para casos excepcionales, por ejemplo me conecto a un servicio como una api o algo externo, e incluso así me gusta nombrar métodos como first(), createOrUpdate() por ejemplo para mantener una consistencia en la legibilidad, me parecen muy interesantes tus posts, interesantes conceptos, saludos!
La realidad es que el uso de repositorios esta reservado a los Data Mapper y en casos en los que requieres acceder a fuentes externas como el que mencionas. Fuera de eso, su uso mas común en Laravel es justificar código y pruebas ya que no agrega nada mas.
Muchas gracias por tu comentario Ariel y esta perfecto que sigas los nombres de los métodos para seguir un standard! Saludos y gracias por visitar el blog.
El patrón repositorio vendría a solucionar el único faltante que tiene laravel con el principio solid. Usar las relaciones de laravel ayudaría a la legibilidad del código.
El patrón repository y el patron ActiveRecord (Eloquent) intentan resolver lo mismo de forma distinta, así que no tiene mucho sentido usarlo con Eloquent. Por otro lado hacer uso de los principios SOLID no requiere que utilices este patrón para que se cumplan. Y si quieres usarlo puedes utilizar otro ORM como Doctrine donde se justifica el uso de los repositorios.
Que es mas recomendable, hacer este proceso o crear una vista en la tabla con todo el query y solo llamarlo, el codigo no estaria mas limpio?
Si, podrías. Pero, como todo, tiene sus ventajas y desventajas. Todo depende de la situación en la que estés.
Las vistas son geniales en los reportes porque no cambian continuamente y los puedes representar como un modelo, pero no las recomiendo para manejar la lógica de negocio porque tendrías que modificar la vista mediante migraciones. Por otro lado es importante tomar en cuenta que las vistas tienen algunas limitaciones en las consultas ya que crea una tabla virtual cada vez que se usan.
Hola, muy interesante; me queda una duda esta forma de utilizar eloquent es agregar más codigo a los modelos ¿ violaria el principio solid Principio de segregación de la interfaz ?
Si, según el caso. Pero siempre puedes crear una nueva clase con los métodos que se relacionen. En mi canal de YouTube hice un vídeo de como enviar los scopes a otra clase.
hola matias podrías dejar el link hacia ese vídeo, muchas gracias por tus aportes!
Hola, muy bueno a resultado este tutorial una duda como sería para un query builder al usar por ejemplo DB::table??
Lo que yo hize fue: dentro del modelo declarar use DB; y puedo utilizar el DB::table o select db:raw
Saludos
Buen día, Matias.
Tengo una duda que me surgió al terminar de leer este articulo.
Tengo un servidor con 5 BD, de los cuales las 5 BD tienen la misma estructura y tablas.
BD: México -> Tabla: Productos
BD: España -> Tabla: Productos
BD: Canada -> Tabla: Productos
BD: EUA -> Tabla: Productos
BD: Francia -> Tabla: Productos
Yo pretendo hacer una búsqueda en cada base de datos a la tabla productos donde busque en varias columnas, ¿Cuál sería la forma mas eficiente de hacerlo? ¿Tengo que crear un modelo por cada tabla?
Nota, no puedo cambiar la estructura de las tablas o modelos, pues es de un sistema comprado.
¡Saludos y excelente articulo!