ALBERT ORTELLS // TECH TESTS

Prueba técnica · World2Meet · Julio 2021

Cómo construí la API de súper héroes

El relato, en primera persona, de cómo abordé la prueba técnica de backend de W2M: las decisiones de diseño, lo que prioricé y lo que dejé fuera por tiempo.

El enunciado pedía una API en Spring Boot 2 / Java 11 para mantener una lista de súper héroes: consultarlos todos, consultarlos por id, buscarlos por un fragmento de su nombre, modificarlos y borrarlos, con persistencia en una H2 en memoria y al menos un test unitario. Además había una lista de puntos opcionales (DDL con librería, anotación de tiempos, gestión de excepciones, tests de integración, Docker, caché, documentación, seguridad) que sabía que no me daría tiempo a cubrir todos, así que decidí desde el principio priorizar lo obligatorio y dejar sentada una base de arquitectura limpia sobre la que esos opcionales pudieran añadirse después sin fricción.

El proceso, commit a commit

13 jul 2021 Initial commit · Create .gitignore

Arranqué el repositorio en local. No hacía falta publicarlo, el enunciado permitía entregarlo comprimido, pero preferí llevarlo por Git desde el minuto uno para que el historial de commits sirviera como evidencia del proceso, tal y como sugería el punto de "se valorará positivamente el uso de TDD... se pueden utilizar los commits para ver el proceso".

13 jul 2021 Spring Initializr

Generé el esqueleto del proyecto y elegí las dependencias pensando ya en varios de los puntos opcionales:

  • Spring Web + Data JPA + Data JDBC + Data REST para la capa de API y persistencia.
  • H2 en memoria, tal y como pedía el enunciado.
  • Liquibase para no escribir el DDL a mano — cubre el opcional de "librería que facilite el mantenimiento de los scripts DDL".
  • MapStruct para no escribir a mano el mapeo entre entidades JPA y los objetos que viajan por la API.
  • spring-restdocs-mockmvc pensando en generar documentación de la API a partir de los tests, aunque al final el tiempo no me llegó para completar esa parte.
15 jul 2021 (mañana) Puntos 1 a 3

Este fue el commit grueso: monté la arquitectura en capas y los tres primeros endpoints de consulta.

  • SuperheroeEntity + changelog de Liquibase (superheroes.sql) que crea la tabla y siembra 10 héroes de ejemplo.
  • Separación Entity (persistencia) / DAO (entrada) / DTO (salida), con SuperheroeMapper de MapStruct entre medias, para no acoplar lo que guardo en base de datos con lo que expongo por la API.
  • SuperheroesRepository sobre JpaRepository, con un método derivado findSuperheroeEntitiesByNameContainingIgnoreCase para la búsqueda por nombre parcial que pedía el enunciado (el ejemplo de "man" → Spiderman/Superman/Manolito).
  • GenericResponse como envoltorio único de respuesta (status, message, data) para que todos los endpoints respondan de forma consistente.
  • URLConstant para centralizar las rutas y no repetir strings mágicos por los controladores.
  • Los tres endpoints GET: todos, por id y por nombre.
15 jul 2021 (mediodía) Update Superheroe

Añadí el PUT para modificar un héroe existente, con validación básica de que el id exista antes de tocar nada.

15 jul 2021 (tarde) Delete superhero

Añadí el DELETE, completando así los cinco requisitos funcionales del enunciado.

16 jul 2021 SuperheroeService Test

Cerré la entrega con los tests unitarios de SuperheroeServiceImpl usando JUnit 5 y Mockito, cubriendo el camino feliz y el de error (200/201 vs 404) de cada método del servicio. El enunciado solo pedía "test unitarios de algún servicio", así que me centré en cubrir bien ese único servicio en vez de repartir esfuerzo en tests superficiales de más componentes.

Decisiones de diseño

Respuesta uniforme

GenericResponse envuelve status, mensaje y datos en todas las respuestas, para que el frontend consumidor tenga un contrato predecible sin tener que interpretar el código HTTP a ciegas.

Capas desacopladas

Entity ↔ DAO/DTO vía MapStruct, controlador delgado que delega en el servicio, y el servicio hablando solo con el repositorio. Cada pieza tiene una única razón para cambiar.

DDL versionado

Liquibase gestiona el esquema y la carga inicial de los 10 héroes de ejemplo, en vez de un schema.sql/data.sql sueltos.

Rutas centralizadas

URLConstant agrupa los fragmentos de ruta para que no queden strings repetidos y desincronizados entre controlador y tests.

Qué quedó cubierto y qué no

Siendo honesto con el resultado final, de los puntos opcionales del enunciado:

El detalle de cómo cerrar cada uno de estos puntos, junto con otras mejoras que veo con perspectiva de los años, está en el documento de mejoras pendientes.