Me llama mucho la atención cómo a lo largo de los años puedo haber trabajado en aplicaciones con .NET en C#, con JavaScript utilizando Angular y Node.js, y también con PHP cuando estaba más centrado en hacer módulos en Drupal, y cómo con todos esos lenguajes y sus respectivos entornos de desarrollo se pueden aplicar las mismas buenas prácticas.
Hay veces que éstas se refieren a procedimientos de trabajo o al modo de estructurar un proyecto; otras hacen referencia a aplicar patrones de diseño e incluso antipatrones para detectar precisamente lo que no se hace bien. En otras ocasiones, son sencillamente prácticas habituales que sigue la mayoría de la gente que usa una tecnología en particular (de ahí, por ejemplo, los proyectos de scaffolding).
Un buen proyecto de software debe oler a buenas prácticas, de principio a fin, desde su arquitectura hasta la evolución de las funcionalidades que se le van añadiendo poco a poco.
Nada peor que una aplicación, proyecto o sistema en el que no se pueda ver claramente cómo están implementadas esas buenas prácticas.
Y es que, además, tienen algo en común: la mayoría son fáciles de implementar.
Y como me gusta escribir… pues voy a ir hablando de ellas, de aquellas que he visto directamente que tienen más impacto en la mejora de la calidad de un proyecto de software.
Un aviso a navegantes: en muchas ocasiones los beneficios de estas recomendaciones no se ven claramente, ni siquiera a corto plazo, y su impacto positivo a medio y largo plazo es más bien sutil. Sólo lo aprendes cuando has cometido todos los errores posibles; entonces se hace la luz y dices algo como: “Ahora sí que lo entiendo”.
Para mí, la implementación de repositorios de datos es una de las prácticas más útiles en cualquier proyecto de software.
El principio general de implementar un repositorio de datos sería el siguiente:
Todos los accesos a los datos que utiliza una aplicación (ya provengan de bases de datos, entidades de datos del mundo cloud, datos en caché, etc.) deben realizarse desde un único punto.
Dependiendo de la tecnología y del tipo de aplicación que se esté desarrollando, ese único punto puede ser un módulo con sus clases, una librería, una API, un servicio REST, etc. Lo importante es que todos esos accesos estén centralizados y bien localizados en el proyecto.
¿Qué beneficios se obtienen de esto? Son muchos, muchísimos; algunos no son fáciles de ver al principio.
En resumen, al centralizar el acceso a los datos conseguimos:
- Cumplir con el principio SoC (separation of concerns o separación de intereses), básico en el desarrollo ágil. De este modo tenemos una parte muy concreta y localizada del proyecto especializada en acceder a los datos que utiliza.
- Evitar duplicar código repitiendo los mismos accesos a los mismos datos a lo largo de la solución. En lugar de ejecutar algo como
SELECT * FROM dbo.users WHERE userId = 20cada vez que se necesita la información de un usuario, tenemos algo mucho más general comodata.getUserById(20)(esto es un sencillo ejemplo en pseudocódigo, ¡eh!). - Simplificar la solución y reducir el número de líneas de código. Al estar encapsulado el acceso a los datos en métodos muy concretos, no necesitamos ensuciar u ofuscar la lógica de negocio de la aplicación con detalles de acceso a los datos (tales como abrir una conexión a la base de datos y cerrarla, etc.), por no hablar de cuando esos accesos son más complejos y necesitamos transacciones o joins anidados.
- Facilitar un análisis sencillo de profiling: con sólo repasar en el objeto repositorio a qué datos se accede y cómo, podemos determinar qué índices hacen falta en bases de datos relacionales e implementar pruebas de rendimiento que se puedan ejecutar continuamente.
- Desacoplar la solución de la manera en que accede a los datos. ¿Y si mañana cambiamos de motor de base de datos? ¿Y si, por escalabilidad, se decide distribuir los datos en diversas bases de datos, incluso con tecnologías distintas? Nada peor que empañar todas las partes de la solución con los detalles del acceso a los datos. Puesto que todo está encapsulado en el repositorio, tan sólo hay que modificar éste. El resto de la aplicación ni se entera del cambio.
- Crear pruebas específicas, unitarias y de rendimiento, ya que la implementación del repositorio está desacoplada.
- Crear mocks con facilidad. ¿Qué pasará cuando la tabla “usuarios” tenga cien mil registros? ¿Aguantará el front-end? Con un repositorio de datos es fácil simular diversas situaciones para anticiparnos a problemas de escalabilidad y, sobre todo, respaldar esas situaciones con pruebas.
- Facilitar el trabajo especializado de la persona encargada del desarrollo de la base de datos y su acceso, al estar centralizados todos los accesos.
- Evitar que las migraciones del repositorio de datos se conviertan en una pesadilla, ya que los accesos no están dispersos por la solución.
Hace un tiempo se hablaba en ciertas tecnologías de la capa DAL (data access layer), que vendría a ser el mismo concepto, pero más ligado a las bases de datos relacionales.
Si se utiliza algún tipo de ORM que crea objetos alrededor de las entidades de datos, ¿deberían esos objetos viajar a lo largo de la lógica de negocio? Esta pregunta me la encuentro recurrentemente, y mi opinión es que no, puesto que de ese modo la aplicación tiene una dependencia clara de la tecnología ORM utilizada. Los objetos que se deberían distribuir a lo largo de la lógica de negocio serían similares, pero no estarían ligados a la tecnología ORM usada (son los llamados data transfer objects o DTO).
A ver, al final hay que aplicar el sentido común (con un poco de experiencia y hasta de suerte…): si trabajamos en un proyecto cuyo ciclo de vida está muy claro, sabemos que terminará y que no va a evolucionar, podemos tomarnos ciertas licencias. Pero si tenemos entre manos un producto o proyecto que crecerá y evolucionará en funcionalidad durante muchos años, entonces sí hay que cuidar mucho este tipo de decisiones y dependencias, así como aplicar con mayor rigor aún todas las buenas prácticas posibles.
Sí, ya sé: es que a veces no hay tiempo, el proyecto viene muy cerrado en horas, etc. De acuerdo: razón de más para aplicar este tipo de buenas prácticas desde el principio, porque sólo así se podrá avanzar en el proyecto con productividad y mayor rapidez.
Todo esto es más fácil de implementar que de describir; sin embargo, un buen proyecto de software que necesite cualquier mecanismo para persistir datos tiene que acceder a ellos a través de un objeto, módulo o librería que implemente esta buena práctica.
Algunos enlaces de interés: