8 de mayo de 2020

Despliega tu API en la nube gratis

Ahora que tenemos nuestra API lista para ejecutarse vamos a ver cómo desplegarla en la nube. Básicamente tenemos que cambiar nuestra BD (ya no nos sirve con H2 en local) y exponer la API públicamente en red. Los servicios que voy a usar para ello son ElephantSQL para la BD y Heroku para la API. Para ello vamos a hacer lo siguiente:
  1. Creo una base de datos en ElephantSQL
  2. Cambio el datasource de JPA y el dialecto org.hibernate.dialect.PostgreSQLDialect. Puedes ver qué debes poner en la documentación de ElephantSQL para Java.
  3. Una vez cambiado eso voy a cargar los datos de los participantes en la BD ya en la nube. Descomento las líneas del main que lo hacen y lanzo la API. Ya podré ver que todo funciona correctamente pero ahora ya no tengo H2.
  4. Una vez cargada la BD y funcionando toca subir la API.
  5. Creo una aplicación en Heroku. Se puede desplegar todo usando el CLI.
  6. Lo más cómodo es enlazar el repositorio de GitHub donde tengamos el código fuente (para que se construya debe tener acceso a todas las dependencias, por eso ya cambiamos la dependencia datos-deportivos a un repositorio). Así incluso podemos tener nuestro despliegue continuo (cada commit se despliega)
  7. Primero tenemos que subir nuestro código con los últimos cambios, pero sin las credenciales de la BD (¡ojo con subir credenciales!)
  8. Una vez enlazado el repositorio ya veremos que podemos desplegar la aplicación. Pero nos faltan las credenciales para conectar con la BD. Vamos a ponerlas como una variable en Heroku para que la use sobrescribiendo lo que haya en el archivo de propiedades.
    DATABASE_URL: postgres://usuario:password@server.com:5432/usuario
  9. Y con esto ya podemos darle a deploy.

Cuando termine de ejecutarse, si todo ha ido bien, podemos acceder a nuestra API RESTful en la url que nos proporcione Heroku. Todo en la nube, sin coste alguno y sin administrar ninguna máquina.

Puedes ver el código hasta aquí en su repositorio.

NOTA: Con el fin de Heroku gratis el 28 de noviembre de 2022, se propone usar como alternativa Railway.

7 de mayo de 2020

Empaquetar la API en un fichero.jar

Ahora que ya tenemos nuestro MVP desde la entrada anterior vamos a ver cómo empaquetarlo todo. La idea es que podamos ejecutarlo desde cualquier máquina con la JVM.

Spring Boot nos proporciona una tarea a nuestro proyecto de gradle que se llama bootJar y que es suficiente con ejecutarla. Para ello podemos usar la pestaña "Gradle Tasks" en Eclipse o simplemente ejecutar la tarea bootJar con el mismo gradle wrapper que tenemos en nuestro proyecto. Para esto último usamos desde la carpeta raíz de nuestro proyecto el comando siguiente:
gradlew bootJar
Si todo va bien tendremos nuestro .jar en la carpeta /build/libs de nuestro proyecto:

Así lo ejecutamos en la consola con el comando:
java -jar datosdeportivosapi-VERSION.jar
Para que se pueda levantar el servicio se necesita tener la BD levantada.

Para cualquier duda dejarlo en los comentarios.

6 de mayo de 2020

Código útil: clase ConfiguracionRest

Como hemos visto en las entradas anteriores, hemos añadido un link a un método personalizado pero de una manera muy manual y nos tocaría añadir otro más. No obstante, el código que se ha utilizado puede generalizarse más teniendo en cuenta lo siguiente:
  1. Los métodos que queremos enlazar tienen la anotación @ResponseBody
  2. Los parámetros que deben formar parte del path los anotaremos con @PathVariable.
  3. Los parámetros que deben formar parte de la query string los anotaremos con @RequestParam.
  4. Podemos configurar a qué recursos queremos añadirles enlaces en su /recursos/search y relacionarlas con su RecursoController que tenga los métodos a los que queremos enlazar.
Siguiendo esa guía podemos usar Reflection para conseguir toda la información y añadir los enlaces haciendo que:
  1. Filtre el recurso para el que debe añadirse los links (sólo los registrados)
  2. Recupere todos los métodos del Controller asociado a ese recurso
  3. Filtre aquellos marcados con ResponseBody
  4. Recupere el link con linkTo pasando como argumentos el valor "(" + nombre + ")" para cada PathVariable (se usan paréntesis para evitar el "escape" de las llaves)
  5. Cambiamos los paréntesis por llaves en las variables de la ruta.
  6. Utilice sólo los nombres de los parámetros RequestParam como query params
  7. Use el nombre del método como rel
El código está en el gist para la clase ConfiguracionRest:

También incluye un filtro CORS (CorsFilter) para permitir cualquier petición y así poder realizar pruebas sin preocuparse de las políticas Cross-Origin por defecto.

Cómo usar el bean correctamente

En nuestro repositorio vamos a eliminar el código de la sesión anterior para sustituirlo por esta clase de configuración que añadimos a nuestra aplicación.

Ahora personalizamos nuestro bean para los links añadiendo al mapa de controladores para enlazar la entidad como clave (PartidoConId.class) y el controlador asociado como valor (PartidoController.class). De aquí se deduce que lo más simple es poner todos los métodos para una entidad en un único Controller.

Si se quieren añadir otros enlaces aparte de los aporta este bean, se puede crear otro bean del mismo tipo en otra clase de configuración y al final todos los enlaces convivirán en el mismo /search.

Puedes encontrar el código hasta aquí en su repositorio.

Este punto finaliza nuestro Producto Mínimo Viable (PMV/MVP) y establece la release v1.0.0. En el README del repositorio se pueden ver las instrucciones para ejecutar la API en local.

5 de mayo de 2020

Rutas con @PathVariable

En esta entrada vamos a rematar la exposición de nuestros endpoints añadiendo variables a la ruta. Esto es muy normal cuando queremos referirnos a un recurso y nuestras rutas pueden tener fragmentos que sigan el patrón /recursos/:id.

En el endpoint expuesto la entrada anterior sólo hemos usado query params. En esta ocasión vamos a pedir los sucesos de un participante entre dos fechas y el id del participante estará en el path. La consulta ya está hecha en nuestro repositorio (SucesoDAO), pero no lo estamos exponiendo. Lo hicimos cuando vimos cómo personalizar rutas con @RestResource. Ésta anotación nos permitía cambiar el fragmento del path que se obtiene en /recursos/search e incluso personalizar los query params con @Param, pero no permite el uso de variables en el path. En este ejemplo queremos que el path sea /search/participante/{id}/entre-fechas.

Para esto vamos a tener que seguir usando @RequestMapping o cualquiera de sus variantes en un controlador. Como es una consulta sobre sucesos voy a crearme la clase SucesoController. El código se parece muchísimo al que vimos en la última sesión y queda así:
@RepositoryRestController
@RequestMapping(path = "/sucesos/search")
public class SucesoController {

    private SucesoDAO sucesoDAO;

    SucesoController(SucesoDAO sucesoDAO) {
        this.sucesoDAO = sucesoDAO;
    }

    @GetMapping("/participante/{id}/entre-fechas")
    @ResponseBody
    public CollectionModel<PersistentEntityResource> getSucesosConIdParticipanteEntreFechas(
            @PathVariable("id") String id,
            @RequestParam Instant comienzo, @RequestParam Instant fin,
            PersistentEntityResourceAssembler assembler) {
        List sucesos = sucesoDAO.findByIdParticipanteAndTemporalBetween(id, comienzo, fin);

        return assembler.toCollectionModel(sucesos);
    }

}
Me voy a centrar en lo nuevo. Si nos fijamos en el path aparece {id}. Cuando ponemos una parte del path entre llaves estamos indicando que se trata de una variable en el path y su nombre es el texto que hay dentro. Para hacer referencia a esa variable se usa @PathVariable pudiendo anotar un parámetro igual que hicimos con @RequestParam.

Para poder usarlo desde la ruta /sucesos/search nos toca modificar nuestro bean que procesa el recurso para búsquedas y añadirlo al código que ya tenemos para cuando el tipo gestionado sea SucesoConId. Esto empeora el mantenimiento así que, como lo prometido es deuda, en la siguiente entrada vamos a ver cómo añadir una configuración que nos descubra estos links personalizados automáticamente.

Puedes conseguir el código hasta aquí en su repositorio.

4 de mayo de 2020

Añadir link a /resources/search

Para conseguir en nuestra API REST el nivel 3, HATEOAS, hypermedia o RESTful hace falta que nuestros enlaces se puedan autodescrubrir. Esto no lo digo yo. El padre de REST explica que hypermedia es una condición mínima para llamar a un servicio RESTful. Una traducción (por google) de lo que ha dicho Roy Fielding al respecto:

Me siento frustrado por la cantidad de personas que llaman a cualquier interfaz basada en HTTP una API REST. El ejemplo de hoy es la API REST de SocialSite. Eso es RPC. Grita RPC. Hay tanto acoplamiento en la pantalla que se le debe dar una calificación X.

¿Qué se debe hacer para dejar claro el estilo arquitectónico REST sobre la noción de que el hipertexto es una restricción? En otras palabras, si el motor del estado de la aplicación (y, por lo tanto, la API) no está siendo impulsado por hipertexto, entonces no puede ser RESTful y no puede ser una API REST. Período. ¿Hay algún manual roto en algún lugar que deba repararse?

Con esto claro, en nuestra API no hay ningún enlace que dirija al método que expusimos en la entrada anterior: no se puede autodescubrir, o conoces la URL o navegando no hay un camino hasta ella. Vamos a añadir un link a nuestro nuevo endpoint para que no se frustre Roy.

Para ello vamos a usar RepositorySearchesResource. Éste bean permite añadir links a todos los que se crean automáticamente por Spring Data Rest. Hay varias formas de hacerlo, aunque las que he encontrado por Internet normalmente usan la versión antigua de HATEOAS (ResourceProcessor como veíamos también en la documentación de la entrada anterior). Voy a hacerlo migrado ya a la versión actual y de la forma que me parece mejor por ser más reutilizable.

Me voy a crear un bean del tipo RepresentationModelProcessor<RepositorySearchesResource> en mi clase de configuración. Es decir, voy a indicar el modo en el que se debe procesar el recurso utilizado para las búsquedas. El código quedaría así:
@Bean
RepresentationModelProcessor<RepositorySearchesResource> searchLinks(RepositoryRestConfiguration config) {
    return new RepresentationModelProcessor<RepositorySearchesResource>() {

        @Override
        public RepositorySearchesResource process(RepositorySearchesResource searchResource) {
            if (searchResource.getDomainType().equals(PartidoConId.class)) {
                try {
                    String nombreMetodo = "getPartidosConParticipanteComo";
                    Method method = PartidoController.class.getMethod(nombreMetodo, String.class,
                            PersistentEntityResourceAssembler.class);
                    URI uri = org.springframework.hateoas.server.mvc.WebMvcLinkBuilder
                            .linkTo(method, null, null).toUri();
                    String url = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
                            config.getBasePath() + uri.getPath(), uri.getQuery(), uri.getFragment()).toString();
                    searchResource.add(new Link(url + "{?txt}", nombreMetodo));
                } catch (NoSuchMethodException | URISyntaxException e) {
                    e.printStackTrace();
                }
            }

            return searchResource;
        }

    };
}
Lo importante de todo este código es que el objeto searchResource se usa para todos los /search. Eso quiere decir que si añadimos un enlace aparecerá en todos ellos. Si queremos añadir el enlace en sólo uno (en nuestro caso para partidos) debemos filtrar para que coincida con el tipo correcto: PartidoConId. Podría ponerse el enlace a mano, pero entonces cualquier cambio posterior nos obligaría a recordar que hay que cambiarlo aquí también.

Lo mejor es recuperar el enlace que apunta a un método. Voy a usar linkTo y le pasaré el método al que quiero apuntar y los parámetros con null (estos son para incluir valores en las variables del path y completar la URL, pero nuestro método sólo tiene query params).

NOTA: Para usar linkTo se puede usar la importación estática para evitar poner su paquete, pero lo he dejado en el código para que quede claro de dónde viene el método.

Lamentablemente, el link no incluye el basePath y hay que añadírselo (como dicen ellos: "por ahora") por eso se construye de nuevo la URI incluyéndolo. Para recuperar ese valor lo mejor es usar RepositoryRestConfiguration que puede ser inyectada en el método.

Ya con la información completa podemos añadir el enlace en searchResource incluyendo los query params.

Si ejecutamos veremos que nuestro enlace aparece al final de los que se han añadido automáticamente.

No obstante, supongo que a cualquiera le dolerá ver esos "magic number" y pensará que debe haber una forma más automática de hacerlo. Personalmente no la he encontrado y tiene sentido (no se puede saber cómo se van a añadir los endpoints porque hay varias formas). Si alguno encuentra algo oficial agradecería que lo compartiera en los comentarios para completar la entrada.

Además en la siguiente entrada voy a añadir otro endpoint usando una variable en el path y también habrá que añadir el link.

Para arreglar esta falta de automatización, después de la siguiente entrada voy a compartir una clase de configuración base que puede utilizarse de manera generalizada en los proyectos igual que hicimos con el jpa-config.xml y explicaré la forma en que debe usarse. Así veremos que sólo con eso detecta y añade este endpoint y el que veamos a continuación que incluye path variable (y es otro parámetro que debemos controlar).

Puedes obtener el código hasta aquí en su repositorio.

Compárteme

Entradas populares