YAUS - Yet Another URL Shortener

2.501 palabras · 10 minutos

09/08/2015

Este proyecto puede ser especialmente interesante para ti si quieres aprender a desarrollar aplicaciones REST con java o quieres familiarizarte algunos conceptos como TDD, BDD o mocking y tener un ejemplo práctico.

He implementado un acortador de URLs desde cero con la intención de plasmar en algún proyecto propio algunas habilidades, ideas y conocimientos que he ido adquiriendo a lo largo de los años.

En este artículo pretendo explicar brevemente determinadas técnicas y tecnologías utilizadas durante su desarrollo, tales como:

Este acortador de URLs, sin embargo, no deja de ser una prueba de concepto:

Sin embargo es funcional, fácilmente extendible, tiene una interfaz agradable y, lo más importante, hace lo que se espera de ella :-)

Aqui tienes una captura de pantalla:



Antes de continuar te invito a que clones el repositorio o, mejor aún, a que hagas un fork.

Índice

1. Módulos

1.1 Módulo persistence

1.2 Módulo service

1.3 Módulo web

1.4 Módulo bdd

2. TDD

3. BDD

4. Mocking

5. Conclusiones



Entorno

YAUS se ha desarrollado en una plataforma con el siguiente software:

$ mvn -version
Apache Maven 3.3.1 (cab6659f9874fa96462afef40fcf6bc033d58c1c; 2015-03-13T21:10:27+01:00)
Maven home: /opt/mvn
Java version: 1.8.0_45, vendor: Oracle Corporation
Java home: /opt/jdk1.8.0_45/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "3.13.0-59-generic", arch: "amd64", family: "unix"

El SCM empleado, como no puede ser de otra manera, ha sido Git sobre GitHub y el IDE utilizado ha sido IntelliJ IDEA.

1. Módulos

La funcionalidad y el código fuente del proyecto han sido distribuidos entre los siguientes módulos según su misión:

                                +--------+         +---------+
                                |  YAUS  |<--------|   bdd   |
                                +--------+         +---------+
                                    ^
                                    |
                +-------------------+---------------+
                |                   |               |
                v                   v               v
        +-----------------+   +-------------+   +---------+
        |   persistence   |   |   service   |   |   web   |
        +-----------------+   +-------------+   +---------+

Si eres un desarrollador java o groovy quizá hayas observado que he empleado maven como sistema de construcción. Puede parecer que maven está quizá un poco "pasado de moda" para hacer desarrollos nuevos y que por ello tal vez hubiese sido más conveniente usar gradle.

Hay varios motivos por los que he utilizado maven:

Si crees que esto y mucho más también se puede hacer con gradle y tienes un ejemplo práctico, por favor, hazte un fork del repositorio y demuéstramelo, me encantará aprender algo nuevo.

1.1 Módulo persistence - [enlace]

Este módulo se encarga del almacenamiento y recuperación de los datos.

En una aplicación "real" estas operaciones se suelen realizar sobre una base de datos relacional (p.e. MySQL), no relacional (p.e. mongoDB) o en memoria (p.e. redis). No obstante, en este caso se han utilizado intencionadamente unas estructuras que se almacenan en memoria y que se pierden al reiniciar la aplicación.

Sin embargo, como en prácticamente todas las aplicaciones en las que he trabajado, YAUS utiliza el patrón DAO a este nivel, con lo que podría incluirse soporte para otros almacenamientos de datos fácilmente.

Para ello basta con realizar una implementación de las interfaces UrlMatcherStore y UrlCounterStore en base al sistema de persistencia que se quiera utilizar. Hay que tener en cuenta que la información de las visitas se almacenan en el POJO Visit.

1.2 Módulo service - [enlace]

Contiene la lógica en sí de la aplicación: genera las urls cortas, realiza algunas validaciones y orquesta el diálogo entre el módulo web y el módulo persistence.

En este caso la lógica es muy sencilla, dado que la implementación de un acortador de URLs en sí así lo requiere. Como todos los desarrolladores sabemos es en esta capa donde se debe definir TODA la lógica de la aplicación, la cual debe ser desarrollada a conciencia usando TDD para, entre otras cosas, emular las invocaciones a sus métodos con un interfaz limpio que no deja ningún caso sin contemplar y empleando mocks que simulen la capa de persistencia.

1.3 Módulo web - [enlace]

Este módulo realiza una doble función:

El API REST puede ser consultado en la siguiente URL: https://github.com/raulexposito/yaus/tree/master/web#rest-api.

En cuanto a la interfaz REST desarrollada en java, la capa que la implementa es muy fina y no contiene nada de lógica, con lo que no merece la pena probarla usando test unitarios ni hacer TDD sobre ella. Por el contrario, es la interfaz que va a servir de entrada y salida de YAUS, con lo que debe ser probada usando BDD.

1.4 Módulo bdd - [enlace]

Su misión es la de interactuar con el sistema del mismo modo que lo haría un cliente. Para ello realiza peticiones considerando que el proyecto es un ente indivisible esperando, en base a unas entradas, obtener unas salidas.

Con BDD, al igual que con TDD, las pruebas deben definirse antes de comenzar la implementación que debe superar las pruebas y, en este caso, deben ser lo más cercanas al comportamiento de los usuarios finales posible. Es un tipo de pruebas únicamente funcional, donde no entran otras comprobaciones como pudiera ser el rendimiento.

Para poder ejecutar este módulo, como es natural, YAUS debe estar en funcionamiento.

2. Test-Driven Development (TDD)

Según su entrada en la wikipedia:

Desarrollo guiado por pruebas de software, o Test-driven development (TDD) es una práctica de ingeniería de software que involucra otras dos prácticas: Escribir las pruebas primero (Test First Development) y Refactorización (Refactoring). Para escribir las pruebas generalmente se utilizan las pruebas unitarias (unit test en inglés). En primer lugar, se escribe una prueba y se verifica que las pruebas fallan. A continuación, se implementa el código que hace que la prueba pase satisfactoriamente y seguidamente se refactoriza el código escrito. El propósito del desarrollo guiado por pruebas es lograr un código limpio que funcione. La idea es que los requisitos sean traducidos a pruebas, de este modo, cuando las pruebas pasen se garantizará que el software cumple con los requisitos que se han establecido.

Para hacer TDD nada mejor que seguir el ciclo Red-Green-Refactor:

Al aplicar TDD podrás comprobar que el código que finalmente va a formar parte de la aplicación incluye muchos métodos y que estos además tienen muy pocas líneas, aparte de muchas otras ventajas como puede ser el disponer de interfaces limpias, de clases con una única responsabilidad, y de un diseño muy claro con componentes fácilmente reutilizables.

En YAUS tienes varios ejemplos. Uno sencillo y que se entiende bien es el de la clase DefaultUrlCounterStore y la clase que incluye sus tests unitarios, DefaultUrlCounterStoreTest. No obstante, en todos los proyectos podrás encontrar tests dentro de los directorios src/test/java, los cuales pueden ejecutarse desde cualquier IDE o desde la línea de comandos con un simple mvn test.

Si quieres saber más sobre TDD y tests quizá te interese el artículo que he escrito sobre el Principio FIRST.

3. Behavior-Driven Development (BDD)


Según su entrada en la wikipedia:

En la Ingeniería de Software, behavior-driven development o desarrollo guiado por el comportamiento (BDD), es un proceso de desarrollo de software que surgió a partir del desarrollo guiado por pruebas (TDD).El desarrollo guiado por el comportamiento combina las técnicas generales y los principios del TDD, junto con ideas del diseño guiado por el dominio y el análisis y diseño orientado a objetos para proveer al desarrollo de software y a los equipos de administración, con herramientas compartidas y un proceso compartido de colaboración en el desarrollo de software.

BDD, al contrario de TDD, centra el desarrollo de pruebas en cómo debe ser el comportamiento final de la aplicación, tratándolo como un todo sin distinguir partes, usándolo del mismo modo que lo haría un cliente final.

Lo más común en estos casos es partir de unos requisitos funcionales, saber qué se espera que haga la aplicación cuando se encuentre funcionando. Dado un escenario concreto, qué resultado se espera en base a una petición. BDD convierte estos requisitos en pruebas que pueden (y deben) ser repetidas automática y constantemente contra el software que estamos construyendo a cada paso que damos.

Al igual que ocurre con TDD, en BDD es necesario desarrollar estas pruebas antes de desarrollar el software que debe cumplirlas, de tal modo que el software estará terminado cuando sea capaz de pasar todas las pruebas.

En YAUS hay un módulo llamado bdd que se encarga de ello y para poder usarlo es necesario que la aplicación se encuentre funcionando. Lo más sencillo es seguir las instrucciones documentadas para ello.

Tienes ejemplos de cómo usar esta técnica en las clases RedirectTest, ShortenerTest y VisitingTest. Recuerda que el API REST puede ser consultado en la siguiente URL: https://github.com/raulexposito/yaus/tree/master/web#rest-api.

4. Mocking

Según su entrada en la wikipedia:

En la programación orientada a objetos se llaman objetos simulados (pseudoobjetos, mock object, objetos de pega) a los objetos que imitan el comportamiento de objetos reales de una forma controlada. Se usan para probar a otros objetos en pruebas unitarias que esperan mensajes de una clase en particular para sus métodos, al igual que los diseñadores de autos usan un crash dummy cuando simulan un accidente.
En los test de unidad, los objetos simulados se usan para simular el comportamiento de objetos complejos cuando es imposible o impracticable usar al objeto real en la prueba. De paso resuelve el problema del caso de objetos interdependientes, que para probar el primero debe ser usado un objeto no probado aún, lo que invalida la prueba: los objetos simulados son muy simples de construir y devuelven un resultado determinado y de implementación directa, independientemente de los complejos procesos o interacciones que el objeto real pueda tener.

Durante la ejecucíón del software muchos objetos interactuan entre sí y los resultados pueden ser distintos en base a las condiciones bajo las que se encuentre cada uno de ellos. Es aquí donde entran los mocks: gracias a ellos es posible conseguir, de forma sencilla, que los objetos con los que interactúa aquel que estamos probando devuelva una respuesta predefinida.

Imaginemos que estemos realizando TDD sobre un objeto A, el cual invoca métodos de B. Existen varios casos en los cuales el uso de mocks sobre B puede ser muy práctico:

En YAUS quizá no hubiera hecho falta usar mocks, pero he creado dos ejemplos sencillos a modo ilustrativo usando el framework Mockito. Puedes encontralos en la clase HashGeneratorServiceTest.

5. Conclusiones

Debo reconocer que me he entretenido llevando a cabo este proyecto. Dada su naturaleza y su alcance la lógica es pequeña y sencilla, lo cual me ha permitido hacerlo en poco tiempo, aunque como todos sabemos el diablo está en los detalles y siempre hay algo que se te resiste un poco sin saber muy bien por qué.

Desarrollar un software desde cero requiere tener una visión global de lo que es el proyecto en sí, concebirlo yendo paso a paso, sabiendo qué quieres hacer, qué no y por qué. Tambien requiere que seas capaz de desarrollar funcionalidades y que seas resolutivo en distintas áreas (preparación de entornos, bases de datos, desarrollo del producto, interfaz del usuario, etc.)

YAUS, aunque es una prueba de concepto, es un proyecto completo en el sentido de que cumple su objetivo. Es fácil de entender, de continuar y de mantener, aunque siempre hay que tener en cuenta que un desarrollador profesional nunca regala su mejor software :-)