Servicio REST con Git(hub), CircleCI, Heroku, Gradle y Spring Boot

Como prometí antes, en este post mostraré como hacer un servicio REST/Json con información geográfica-administrativa de Chile con Gradle y Spring Boot 2, entre otras herramientas.

Para poder completar el tutorial deberán tener cuentas creadas en los servicios que quieran usar (Github, Heroku).

Algunas de las instrucciones y/o ejemplos se demostrarán en terminales Bash o algún otro software de sistemas operativos GNU/Linux. No sé hacer estas cosas en otros sistemas operativos por lo que les dejo la tarea a los que quieran usar un entorno de desarrollo diferente.

El repositorio

A pesar de que el orden lo pueden cambiar, yo prefiero comenzar por el repositorio. Para crearlo, deben ingresar a su cuenta Github, y buscar un boton New Repository o la opción New Repository en un menú + cerca de la esquina superior derecha de su home.

Luego deben ingresar los datos de su nuevo repositorio y estarán listos. Luego, en su computador, clonen el repositorio para comenzar a trabajar. Debe usar la URL de clonación proporcionada por Github:

Configuración de Gradle

Para comenzar con Gradle en nuestro projecto necesitamos crear el wrapper y luego inicializar el proyecto como sigue:

Con esto ya tenemos la configuración inicial de Gradle y sería un buen momento para subir esto a nuestro repositorio:

Configuración de Spring Boot

Para comenzar con Spring Boot 2 básicamente tenemos que agregar la dependencia en nuestra configuración de Gradle y crear la clase para la aplicación. Al momento de escribir este artículo aún no existe una release final de Spring Boot 2, por lo que usaremos el milestone 7, que es el último disponible.

Nuestra configuración de Gradle es la configuración de ejemplo generada por este mismo a la hora de ejecutar la tarea init, Por lo que tendremos que limpiar algunas cosas y quitar comentarios innecesarios para dejar nuestro build.gradle como sigue:

buildscript {
  repositories {
    jcenter()
    maven { url 'http://repo.spring.io/snapshot' }
    maven { url 'http://repo.spring.io/milestone' }
  }
  dependencies {
    classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.0.0.M7'
  }
}

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

jar {
  baseName = 'chile-data-rest'
  version =  '1.0-SNAPSHOT'
}

repositories {
  jcenter()
  maven { url "http://repo.spring.io/snapshot" }
  maven { url "http://repo.spring.io/milestone" }
}

dependencies {
  compile 'org.springframework.boot:spring-boot-starter-web'
  testCompile 'org.springframework.boot:spring-boot-starter-test'
}

mainClassName = 'io.rebelsouls.chile.ChileGeoService'

El proyecto de ejemplo incluye una clase principal y una prueba unitaria que podemos eliminar y, en su lugar, agregar esta clase principal:

package io.rebelsouls.chile;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ChileGeoService {

  public static void main(String[] args) {
    SpringApplication.run(ChileGeoService.class, args);
  }
}

Con esto ya tenemos todo lo necesario para comenzar. Podemos ejecutar nuestra aplicación con la tarea run de Gradle:

Ahora nuestra nueva aplicación funciona, pero hace nada. Si navegamos a http://localhost:8080/ podremos conseguir un mensaje de error porque aún no tenemos código para tratar requests entrantes.

El controlador

Para el controlador vamos a necesitar la biblioteca que publiqué en mi artículo anterior. Para eso declararemos el repositorio y la dependencia en la configuración de Gradle:

repositories {
  maven { url "https://mymavenrepo.com/repo/RsIzk5ozSSaqZE2h2UZJ/" }
}

dependencies {
  compile 'io.rebelsouls.lib:chile-data:1.1'
}

Teniendo esto, podemos escribir un controlador simple para listar las regiones que tenemos:

package io.rebelsouls.chile.web;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import io.rebelsouls.chile.Region;

@RestController
@RequestMapping("/geo")
public class GeoInfoController {

  @GetMapping("")
  public ResponseEntity<Map<String, String>> getRegiones() {
    Map<String, String> values = new HashMap<>();
    Stream.of(Region.values()).forEach(r -> values.put(r.name(), r.getNombre()));
    return new ResponseEntity<>(values, HttpStatus.OK) ;
  }
}

Ahora podemos ejecutar nuevamente nuestro proyecto con ./gradlew run y, si apuntamos el navegador a http://localhost:8080/geo deberíamos ver algo como esto:

El código completo del controlodor para provincias y comunas lo pueden completar ustedes mismos o mirar la implementación que está en el repositorio de este ejemplo.
Este es un buen momento para hacer commit y push de nuestro código.

CircleCI

Para proyectos en que van a trabajar más de una persona, e incluso si van a trabajar solos con la ayuda de pruebas automáticas en sus pipelines, un servicio de integración continua es de mucha ayuda. Hay una amplia variedad en el mercado, pero sin duda uno de los más fáciles de integrar con Github es CircleCI. Para comenzar deben crearse una cuenta (si es que aún no la tienen) e iniciar sesión aquí. Pueden acceder fácilmente con el login via Github.

Una vez dentro, tienen que revisar que su cuenta esté conectada con Github en la página de Account Integrations, que está bajo la sección User Settings. Si las cuentas no están conectadas, en esa página tendrán la información que necesitan para poder hacerlo. Si su repositorio pertenece a alguna organización de Github, deben otorgar acceso a CircleCI a la organización desde Github.

Una vez teniendo las cuentas conectadas, debemos ir al menú Projects y luego hacer click en Add Project:

Luego deben hacer click sobre el botón Setup Project, que está próximo a su repositorio:

Finalmente deben hacer click en el botón Start building, dejando las opciones por defecto que deberían ser:

  • Operating system: Linux
  • Platform: 2.0
  • Language: Gradle (Java)

La primera construcción del proyecto debería tomar más tiempo de lo normal porque CircleCI tendrá quedescargar todas nuestras dependencias (alrededor de 2 minutos.) Pasado ese tiempo la construcción debería ser exitosa pues no tenemos pruebas que digan lo contrario. Vamos a cambiar esto agregando pruebas de integración para revisar nuestro controlador. Recuerden crear este archivo en el directorio de fuentes de prueba (src/test/java) para que Gradle lo considere parte de las pruebas.

package io.rebelsouls.chile.web;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
@RunWith(SpringRunner.class)
public class GeoInfoControllerTest {

  @Autowired
  MockMvc mock;
  
  @Test
  public void shouldReturnListOfRegion() throws Exception {
    mock
      .perform(get("/geo"))
      .andExpect(status().isOk())
      .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(jsonPath("$.Coquimbo").value("Coquimbo"));
  }
}

Ahora podemos ejecutar nuestra prueba localmente con Gradle (./gradlew test):

Si hacemos commit y push de nuestra prueba, CircleCI debería ser notificado de nuestro cambio y debería ejecutar nuevamente, esta vez ejecutando nuestra prueba para revisar que todo esté bien. Les recomiendo jugar un poco con el código, subir una versión que no compile o que no pase las pruebas para que vean como CircleCI notifica de estos casos. Pueden revisar que sus pruebas fueron ejecutadas localmente abriendo en su navegador el archivo build/reports/tests/test/index.html, donde deberían ver algo como esto:

Heroku

Ya teniendo un pipeline simple con integración continua ya podemos pensar en un servidor donde correr nuestro servicio. Heroku es un de tantos servicios PaaS que ofrece, entre otros servicios, entornos para correr nuestras aplicaciones sin invertir muchos esfuerzo en la infraestructura. Uno de los productos que ofrece es la posibilidad de desplegar nuestra app en un servidor gratuito, con la limitancia de no poder escalar y que además es un servidor que duerme cuando no recibe carga en un determinado período. Este producto es perfecto para montar entornos de prueba.

Para comenzar con Heroku primero deben crearse una cuenta (si no la tienen aún) e ingresar aquí. Una vez en el dashboard, hay que hacer click en el menú New y luego en la opción Create new app. Tienen que inventar algun identificador de aplicación que esté disponible (este ID será parte de la URL de nuestra app.) Una vez creada la aplicación, debemos ir a la sección Deploy, escoger la opción GitHUb y luego presionar sobre el botón Connect to GitHub:

Una vez conectado, Heroku debería permitirnos buscar repositorios. Simplemente ingrasamos el ID que inventamos para nuetra aplicación, presionamos en Search y seleccionamos el repositorio dentro de los resultados:

Sólo nos queda habilitar el deploy automático y la integración con CircleCI (Wait for CI to pass before deploy) para asegurarnos que sólo se desplieguen las construcciones que pasen con éxito la integración continua.

¡Y ya está! Ahora podemos hacer un deploy manual para subir nuestra primera versión haciendo click en Deploy Branch. Una vez terminado el deploy (demora unos minutos) podemos navegar a http://<id_app>.herokuapp.com/geo para probar nuestro nuevo servicio. Si todo sale bien ya estaremos navegando en nuestro entorno de pruebas. Recuerden que después de 30 min (aprox.) de inactividad el servidor dormirá. Cuando recibe un request estando durmiendo, Heroku redesplegará nuestra app a un nuevo servidor para servir el request, que volverá a dormirse a los 30 mins.

Ahora tenemos una copia local de código que está conectada a un repositorio remoto GitHub. Cuando subimos código al repo, GitHub notifica a CircleCI para la integración y verificación del nuevo código usando Gradle. Una vez pasada la verificación, Heroku es notificado para construir una nueva versión con Gradle, generando un Jar ejecutable con Spring Boot para subir a nuestro servidor. Con esto ya podríamos dar por cumplido el objetivo de este artículo. Cualquier feedback es bienvenido en los comentarios. Pueden probar el servicio final aquí.

Agregar un comentario

Su dirección de correo no se hará público. Los campos requeridos están marcados *