Han pasado seis meses desde que comencé a trabajar en una empresa que creaba una nueva aplicación empresarial. Mi nuevo equipo sigue algunas prácticas ágiles, como la programación en pares y el desarrollo basado en pruebas (TDD), ¡y tengo la sensación sincera de que el sol brilla para nosotros!
Bueno, casi.
Hay algunos problemas a los que me he enfrentado tanto ahora como en experiencias industriales pasadas sobre el tema de la creación de aplicaciones empresariales sólidas que me gustaría explicarles en este artículo.
Además, también propondré una metodología simple basada en la prueba primero para crear aplicaciones empresariales que mejoren la comunicación del equipo y promuevan entregas de código de alta calidad más rápidas.
Sin más preámbulos, ¡vamos al grano!
Las prácticas ágiles son muy beneficiosas para la creación rápida de prototipos de software. TDD se encuentra en el centro de tales prácticas, proporcionando software con una propiedad primordial: robustez. Escribir pruebas por adelantado obliga a los desarrolladores a pensar en el comportamiento esperado y excepcional de los componentes de software que construyen, mejorando la calidad del código y asegurando el cumplimiento de los requisitos funcionales.
Además, TDD es una práctica poderosa que permite a los desarrolladores no tener miedo de corregir, limpiar y adaptar su código a las actualizaciones de requisitos funcionales . Y eso es todo genial. Sin embargo, no es tan fácil llevar TDD a la práctica.
Al comienzo de la construcción de cualquier nueva aplicación empresarial, nosotros (los desarrolladores) recopilamos varios requisitos funcionales. Luego, derivamos un conjunto de casos de uso que satisfarán dichos requisitos funcionales. Después de eso, desarrollamos componentes de software ubicados en diferentes capas, desde la más alta hasta la más baja, profundizando hasta el corazón de la aplicación, es decir, su modelo de dominio. Esa es la definición del proceso de desarrollo de software de arriba hacia abajo .
Sin embargo, el proceso de desarrollo de software de abajo hacia arriba encaja mejor con TDD. En comparación con la alternativa top-down, bottom-up es un enfoque más pragmático, ya que nos permite a nosotros, los desarrolladores, cubrir gradualmente todos los niveles de direccionamiento indirecto, comenzando con el más básico (es decir, el modelo de dominio) y avanzando gradualmente hacia capas más altas de abstracción. Permite la producción de bases de código de aplicación más sólidas, lo que a su vez nos hace ganar una gran confianza en nuestro trabajo. Sin embargo, para aplicar TDD en un enfoque de arriba hacia abajo, primero se deben escribir pruebas para los componentes de software ubicados en las capas superiores. De esta manera, los desarrolladores no necesitan simular ninguna dependencia de ningún componente de capa inferior, ya que simplemente no existen todavía.
La necesidad de crear tales dependencias ya es un problema porque burlarse de los componentes de la capa inferior no siempre es posible o, en el mejor de los casos, parece contrario a la intuición, por ejemplo, imagínese teniendo que burlarse de la lógica de un objeto de dominio para producir un prueba de componente de servicio.
Además, personalmente dudo que traiga algún valor, ya que creo que la validación de componentes intermedios siempre debe ejercer todas las dependencias excepto las de servicios externos (por ejemplo, una base de datos), que se pueden burlar. Más importante aún, debido a la complejidad inherente para realizar requisitos funcionales no triviales como código, uno no comprende completamente las implicaciones técnicas que tienen algunos requisitos funcionales dados en el modelo de dominio hasta que uno comienza a razonar sobre ellos durante la implementación del modelo de dominio.
Una vez más, comenzar a escribir pruebas de componentes de software intermedios no aporta mucho valor, ya que es probable que muchas de esas pruebas (si no todas) se arrojen a la basura una vez que se implementen los artefactos de software de capa inferior.
Además, he visto a desarrolladores de software (especialmente a compañeros de equipo junior) darse por vencidos e implementar alguna prueba de concepto para el caso de uso en cuestión sin escribir ninguna lógica de validación. Esto está utilizando la práctica de código primero, que anula el propósito de TDD. Además, sin seguir las prácticas adecuadas de entrega continua, existe un alto riesgo de terminar enviando código no validado a nuestro repositorio de control de versiones.
Entonces, ¿cómo podríamos aplicar TDD de manera efectiva en la producción de aplicaciones empresariales dado un conjunto de requisitos funcionales?
Hay mucha literatura sobre arquitectura hexagonal en Internet. Recomendaría especialmente leer el libro blanco sobre el tema escrito por Alistair Cockburn.
Para el propósito del presente artículo, permítame contarle una breve historia de la vida real destinada a explicar brevemente la motivación y los principales beneficios de la arquitectura hexagonal: En los muchos años que he estado desarrollando aplicaciones empresariales he visto a muchas personas ( incluyéndome a mí mismo) iniciando nuevos proyectos centrándonos en otros temas más que en nuestra verdadera misión. Tal misión consiste en aportar valor real a las empresas para las que trabajamos. Ese valor está en la lógica de dominio de nuestras aplicaciones.
Parafraseando al tío Bob en su libro Clean Architecture, todo lo demás es una distracción, un detalle de implementación que puede (y debe) posponerse, idealmente hasta el final del desarrollo. Ejemplos de detalles de implementación son tecnologías de base de datos, lógica de controlador o tecnología frontend. Incluso el marco de back-end es un detalle de implementación que podríamos elegir más adelante en el proceso de desarrollo si realmente quisiéramos. La arquitectura hexagonal, también llamada puertos y adaptadores , es un patrón arquitectónico destinado a desacoplar la lógica central de la aplicación de software de los detalles de implementación externos.
Los desarrolladores debemos centrarnos en la lógica central de las aplicaciones empresariales y posponer la implementación de la lógica requerida para comunicarse con servicios externos. Para lograr ese objetivo, todo lo que realmente necesitamos hacer es escribir algunas interfaces (los llamados puertos ) y simular los componentes (los llamados adaptadores ) que realmente se comunican con los servicios externos. Por lo tanto, la implementación del adaptador puede venir más adelante en el proceso de desarrollo. Y cuanto más tarde lleguen, mejor, ya que todos los conocimientos que obtenemos mientras producimos la lógica central resultan realmente útiles durante la toma de decisiones sobre qué tecnologías elegir.
La siguiente imagen es una de las muchas ilustraciones existentes de Arquitectura Hexagonal:
Considere los elementos que componen el hexágono interior. Aparte del modelo de dominio, también hay una capa de servicios de aplicación . Estos componentes de software no especifican ninguna lógica de dominio. En su lugar, son simples coordinadores de la lógica del adaptador y del modelo de dominio. Un servicio de aplicación realiza un caso de uso que se ocupa de un subconjunto de requisitos funcionales para la aplicación empresarial. Este es un dato importante a tener en cuenta para lo que viene a continuación.
Como dije anteriormente, aplicar TDD es más fácil cuando se sigue el proceso de desarrollo de software de abajo hacia arriba. Sin embargo, a muchos de nosotros nos resulta más fácil razonar sobre el diseño de un sistema siguiendo el enfoque de arriba hacia abajo. Y aunque parezca que estamos incurriendo en un conflicto de intereses, está bien porque podemos empezar a diseñar (es decir, dibujar en pseudocódigo o algún diagrama UML) los servicios de nuestra aplicación de arriba hacia abajo sin escribir una sola línea de código para ellos; no hasta que completemos la implementación del modelo de dominio.
Antes de comenzar a codificar, podríamos interpretar los servicios de aplicaciones como algunas pautas de diseño de software para realizar segmentos verticales de las aplicaciones empresariales que vamos a construir. Cada segmento vertical representa la ruta de ejecución completa de un caso de uso, desde la acción realizada por un servicio externo ascendente o un usuario en una interfaz de usuario hasta cualquier operación ejecutada en un servicio externo descendente. Cuando terminamos con el diseño de un servicio de aplicación, hemos identificado qué adaptadores y componentes de dominio necesitamos implementar. Ahora que tenemos visibilidad en el modelo de dominio, podemos implementar sus componentes fundamentales aplicando TDD.
Luego, podemos implementar el servicio de la aplicación siguiendo el enfoque de prueba primero, creando un puerto a cualquier adaptador de servicio externo y simulando su implementación real. Para cuando hayamos terminado con la implementación del servicio de aplicación y el modelo de dominio relacionado, podemos asegurarnos de que dicha implementación es válida, es decir, libre de errores y que cumple con sus requisitos funcionales. Finalmente, podemos implementar la lógica del adaptador, aplicando también TDD.
Esta metodología permite la implementación rápida y gradual de aplicaciones empresariales, lo que permite a los desarrolladores ganar confianza en la validez de todos los componentes que crean sin descartar ninguna prueba. Además, no impone ninguna restricción sobre las actualizaciones de requisitos funcionales.
El siguiente diagrama de flujo muestra la metodología para escribir la lógica de un caso de uso. Tenga en cuenta que no hablo sobre tipos de pruebas específicas ya que este es un tema bastante controvertido, aunque recomendaría seguir las convenciones y la terminología utilizada en el artículo La pirámide de pruebas prácticas .
La metodología propuesta simplifica la distribución de tareas en equipo, sin importar si trabajan solos o en parejas. Algunos de sus pasos se pueden realizar en paralelo, por ejemplo, un compañero/pareja puede construir la lógica central burlándose de cualquier referencia a una dependencia externa, mientras que otro puede estar trabajando en el desarrollo del código de integración con dicha dependencia externa, ya que estas dos partes son desacoplados, acortando así el tiempo de entrega. Todo lo que necesitan hacer es transmitir alguna API para la dependencia externa realizada como un puerto. Tanto la dependencia externa simulada como la real deben implementar la interfaz de puerto antes mencionada. De manera similar, otro equipo/compañero/par puede implementar la parte frontal para el caso de uso, si eso es lo que exige la aplicación empresarial.
Por otro lado, debido a su naturaleza estructurada, es fácil comunicar el estado de desarrollo de un caso de uso concreto entre pares. Al entregar el desarrollo de un caso de uso, la entidad que se va simplemente puede señalar el nuevo servicio de aplicación actual que estaba diseñando o implementando. Luego, la nueva entidad puede simplemente rastrear cualquier cosa que ya se haya creado en la lógica del adaptador o del dominio en la base del código.
Una nota final sobre los detalles de implementación: Podríamos actualizar nuestro modelo de dominio para especificar algunos de esos detalles escribiendo anotaciones/decoradores de la tecnología de la base de datos y recursos de validación de datos de entrada del marco subyacente que se ajuste mejor a la naturaleza de nuestra aplicación. Sin embargo, lo desaconsejaría, ya que sería filtrar detalles de implementación en nuestro modelo de dominio, lo cual no es la mejor práctica, ya que los detalles de implementación y el modelo de dominio tienden a cambiar por diferentes razones y frecuencias. Por otro lado, como explica Vaughn Vernon en su libro Implementing Domain Driven Design (DDD), la arquitectura hexagonal está estrechamente relacionada con DDD. Sin embargo, no es necesario que siga el conjunto de prácticas y patrones de DDD para crear aplicaciones empresariales complejas que se basan en la arquitectura hexagonal, aunque le recomiendo encarecidamente que lo haga. Pero esas decisiones, después de todo, dependen totalmente de usted.
El desarrollo basado en pruebas es una práctica poderosa en la construcción de aplicaciones empresariales sólidas. TDD se aplica mejor siguiendo el proceso de desarrollo de software de abajo hacia arriba. Sin embargo, debido a que los desarrolladores están acostumbrados a razonar sobre tales aplicaciones siguiendo un enfoque de arriba hacia abajo, aplicarlo en la práctica es un desafío. Este artículo expone una metodología simple para ayudar a los desarrolladores a aplicar TDD de manera efectiva en el desarrollo de aplicaciones empresariales.