Technical
Cómo gestionar las implicaciones molestas de cambiar fuentes de datos
Rafael Cavalheiro · 20 de noviembre de 2022
Hablemos de un escenario común en consultoría de IA. El cliente proporciona acceso a fuentes de datos en formatos como CSV o bases de datos que no están en un entorno de producción. ¿Por qué? Generalmente, están explorando el valor del proyecto, no quieren divulgar demasiados datos y desean prevenir problemas técnicos en las etapas iniciales. ¡Lo entendemos!
Luego, validamos la idea y desarrollamos una solución que resuelve correctamente el desafío en pilotos internos. Ahora, el cliente está entusiasmado y quiere llevarla a producción… ¡oh, no! Necesitas cambiar todo el código para adaptarte al nuevo entorno.
¿Cómo podemos evitar que esto suceda? Aquí es donde el patrón Repository puede ser tremendamente útil, ayudándonos a ahorrar tiempo y esfuerzo.
Patrón Repository: ¿Qué es?
El patrón Repository es una forma bien pensada y documentada de gestionar fuentes de datos.
Consiste en una abstracción sobre el almacenamiento persistente, ocultando los detalles aburridos del acceso a datos fingiendo que todos nuestros datos están en memoria.
Como el nombre nos indica, el personaje principal de este patrón de diseño es el repositorio. Los repositorios son un conjunto de objetos y clases que nos ayudan a encapsular la lógica necesaria para acceder a las fuentes de datos.
Los repositorios se sitúan entre el modelo de dominio y las fuentes de datos, permitiéndonos desacoplar nuestra capa de modelo de la capa de datos.
¿Qué problemas ayudará a resolver el patrón Repository?
Consideremos un escenario donde tenemos un sitio web para una tienda en línea con varias páginas. Una página puede ser la página del Producto y otra puede ser la página Mis Pedidos. En ambas páginas, hay un encabezado que da la bienvenida al usuario con un cuadro de texto que dice "¡Bienvenido John Doe!".
Teniendo en cuenta la solución actual, analicemos los problemas con ese diseño:
El primer problema es que tanto MyOrdersController como ProductsController implementan su método privado para obtener el usuario. En programación, esto se denomina duplicación de código que viola el principio DRY (No te Repitas). Al hacer esto, agregamos complejidad a nuestro código, lo que hace que sea más difícil realizar cambios, lo que lleva a gastar más tiempo y esfuerzo manteniendo el código.
Por ejemplo, imaginemos que necesitamos cambiar la forma en que accedemos al Usuario (por ejemplo, cambio de BD). La solución actual requiere cambios en ambos métodos, lo que significa que gastamos más tiempo analizando el impacto de un cambio de BD, ya que hay más de un lugar en el código donde debemos mirar.
El segundo problema con este diseño es que estamos pidiendo a los controladores que gestionen y comprendan cómo acceder a los datos.
En este caso, dado que los datos se almacenan en una Base de Datos, eso significa que los controladores son responsables de gestionar cosas como conexiones a BD y cadenas de conexión.
Esto significa que tenemos una arquitectura fuertemente acoplada, ya que estamos vinculando nuestros controladores a la infraestructura utilizada para almacenar nuestros datos, lo que se traduce en código menos flexible y difícil de mantener.
El tercer problema es que el código es difícil de probar. Imaginemos que nuestra fuente de datos es una base de datos SQL Server. Si quisiéramos probar nuestro código, necesitaríamos configurar SQL Server, obtener la cadena de conexión, configurar Entity Framework, generar la Base de Datos de Prueba, insertar los datos y solo entonces podríamos probar el código, validar los errores y actuar sobre ellos. Esta cadena de acciones no es práctica en absoluto y dificulta escribir código limpio y comprobable.
¿Cómo implementar el patrón Repository?
Veamos cómo podemos utilizar el patrón Repository para mejorar nuestro diseño.
Como se describe al principio de este artículo, el patrón Repository trata de abstraer las fuentes de datos de la capa de aplicación. ¿Qué beneficios aporta este diseño?
En primer lugar, puedes ver que toda la lógica relacionada con el acceso a datos está ahora centralizada en un User Repository y ya no tenemos problemas de duplicación de código. De esta manera, nuestro código respeta el principio SOLID de DRY. Ambos controladores llaman al mismo método de un único repositorio para obtener la información del usuario. Con este enfoque, nuestro código se mantiene mejor ya que simplificamos nuestra arquitectura, haciéndola más flexible y fácil de cambiar.
Otro beneficio es que la capa de aplicación está ahora desacoplada de la capa de infraestructura. Eso nos da la posibilidad de cambiar ambas capas de forma independiente.
Lo que también hicimos fue cambiar la responsabilidad de interactuar con la fuente de datos de los controladores al repositorio. Eso nos permite usar interfaces para definir el comportamiento que nuestros controladores pueden esperar del repositorio y, de esa manera, encapsulamos los detalles de implementación de la fuente de datos. Con estas mejoras, ganamos flexibilidad y escalabilidad al considerar decisiones de diseño sobre cómo acceder a los datos. Por lo tanto, si necesitamos cambiar la forma en que accedemos a nuestros datos (API, NOSQL, archivo CSV, etc.), solo necesitamos cambiar el repositorio, sin preocuparnos de romper la capa de aplicación.
¿Cuáles son las 3 mejores prácticas implementando el patrón Repository?
Veamos algunas de las mejores prácticas al implementar el patrón Repository.
Implementar operaciones CRUD
Una buena práctica es implementar operaciones CRUD en el repositorio. Siempre que implementemos el patrón Repository, debemos intentar diseñar los repositorios para que funcionen con operaciones CRUD. Esto facilita la forma en que interactuamos con el repositorio. Por ejemplo, en el diseño anterior del User Repository, podríamos tener métodos como:
- get_user(id)
- list_users(**filters)
- add_user(**kwargs)
- update_user(**kwargs)
- delete_user(id)
Un repositorio por objeto de negocio
Otra buena práctica es tener un repositorio por objeto de negocio.
El principio de Responsabilidad Única dice que "cada clase debe tener una sola razón para cambiar", así que teniendo eso en consideración, tiene sentido que tengamos un repositorio para cada objeto de negocio en nuestros datos. Por ejemplo, si tenemos datos sobre usuarios, productos y pedidos, debemos implementar 3 repositorios:
Proporcionar un contrato o interfaz
Como puedes notar en la figura anterior, para cada repositorio hay un repositorio abstracto, que es una clase abstracta. Una clase abstracta es una clase que no puede ser instanciada. Sin embargo, podemos crear otras clases que hereden las propiedades de la clase abstracta. Puedes pensar en la clase abstracta como un plano o un contrato, donde definimos todos los métodos que una clase hija heredará. Lo que eso significa es que la clase abstracta no tendrá ninguna implementación lógica, solo el esqueleto de la clase. Esa lógica será implementada en una clase hija que hereda de la clase abstracta.
¿Por qué es esta una buena práctica? Bien, esto nos ayuda a mantener una arquitectura débilmente acoplada, contribuyendo a la abstracción de la última capa donde tenemos nuestras conexiones a fuentes de datos. De esta manera, no necesitamos inyectar directamente clases concretas en nuestros controladores, solo pasamos una interfaz (la clase abstracta), así los controladores saben qué esperar de la capa de infraestructura.
Esto nos permite usar diferentes implementaciones de la misma interfaz dentro de nuestra arquitectura. Un ejemplo realmente bueno es que, para propósitos de prueba, solo necesitamos reemplazar la base de datos con datos en memoria como un retorno de una lista o un array, por ejemplo, sin necesidad de cambiar la forma en que los controladores interactúan con la interfaz.
Conclusiones
Como se detalla en este artículo, el patrón Repository puede ser transformador para desarrolladores de software o científicos de datos. Ya sea desarrollando una solución para un cliente, cuando a menudo no tenemos la fuente de datos de producción en las etapas iniciales, o desarrollando un POC para un producto usando datos simulados, el patrón Repository nos ahorra tiempo y esfuerzo a largo plazo y hace nuestro código más limpio y fácil de mantener, teniendo una arquitectura débilmente acoplada que nos permite cambiar las capas de aplicación e infraestructura de forma independiente.
