1. Introducción

Hola a todos y bienvenidos de nuevo post de nuestro blog!. En esta ocasión hablaremos de Consul como sustituto de Eureka y del servidor de configuración. Y es que Consul nos permitirá, entre otras cosas, llevar a cabo el registro y descubrimiento de servicios. Además tiene la capacidad de almacenar pares clave/valor que podrán ser recuperados desde nuestras aplicaciones. Este post es análogo al que publicamos hace unos meses sobre Eureka + Ribbon + Feign, pero en lugar de utilizar Eureka utilizando Consul. Así que si no te lo has leído te invito a que le eches un vistazo antes de empezar con este.

Os podéis descargar el código de ejemplo de mi GitHub aquí.

Tecnologías empleadas:

  • Java 8
  • Gradle 3.1
  • SpringBoot 1.5.2.RELEASE
  • Spring 4.3.7.RELEASE
  • SpringCloud 1.1.5.RELEASE
  • Consul 0.9.2

2. Consul

Como ya hemos comentado Consul es un tecnología que nos permite llevar a cabo el registro y el descubrimiento de nuestros servicios así como la persistencia y recuperación de las propiedades de nuestra aplicación. Utilizaremos docker para desplegar una instancia de Consul como viene siendo habitual en nuestro blog.

Se muestra el fichero docker-compose.yml que nos permitirá correr un contenedor docker de Consul

consul:
  container_name: my-consul
  image: consul:latest
  ports:
    - "8500:8500"

Para arrancar nuestro contenedor basta con

docker-compose up

Accedemos a http://localhost:8500 y podemos ver la administración de Consul!

Propiedades Key/Value

Una de las características interesantes de Consul es su capacidad para persistir y devolver propiedades comportándose como servidor de configuración. Para dar de alta dichas propiedades basta con acceder a la sección KEY/VALUE y empezar a crear nuestras propiedades.

3. Ficheros de configuración

Hacemos uso de las dependencias spring-cloud-starter-consul-all y spring-cloud-starter-feign dentro de nuestro build.gradle

group 'com.jorgehernandezramirez.spring.springboot'
version '1.0-SNAPSHOT'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'maven'
apply plugin: 'spring-boot'

dependencyManagement {
    imports {
        mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR2'
    }
}

sourceCompatibility = 1.8

springBoot {
    mainClass = "com.jorgehernandezramirez.spring.springcloud.Application"
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.springframework.cloud:spring-cloud-starter-consul-all'
    compile 'org.springframework.cloud:spring-cloud-starter-feign'
}

En el fichero application.yml establecemos el host y puerto de Consul. Además establecemos cómo queremos que nuestro microservicio se registre. Es importante destacar que hay que indicarle a Consul a través de la propiedad spring.cloud.consul.discovery.healthCheckPath la url de chequeo del estado de la aplicación.

server:
   port: 8080

spring:
   cloud:
      consul:
         host: localhost
         port: 8500
         discovery:
            port: 8080
            prefer-ip-address: true
            healthCheckPath: /health
            healthCheckInterval: 5s

En el fichero bootstrap.yml indicamos el nombre de la aplicación y el formato de propiedades que esperamos que Consul nos devuelva. En nuestro caso indicamos el formato YAML.

spring:
  cloud:
    consul:
      config:
        format: YAML
  application:
     name: springcloudwebtest

Por defecto SpringBoot irá a buscar las propiedades en las siguientes rutas (se muestran por prioridad)

    /config/[NOMBRE_MICROSERVICIO],[PERFIL]
    /config/[NOMBRE_MICROSERVICIO]
    /config/application,[PERFIL]
    /config/application

Sin embargo, si lo que queremos es definir un yaml dentro de Consul en lugar de tener que dar de alta una a una las propiedades de nuestra aplicación deberemos definir dentro de bootstrap.yml la propiedad spring.clolud.consul.config.format con el valor YAML tal y como se ha mostrado anteriormente. En este caso dichos yaml se deben crear en alguna de las siguientes rutas dentro de consul (se muestran por prioridad)

    /config/[NOMBRE_MICROSERVICIO],[PERFIL]/data
    /config/[NOMBRE_MICROSERVICIO]/data
    /config/application,[PERFIL]/data
    /config/application/data

En nuestro caso definiremos nuestro YAML dentro de la ruta /config/application/data

4. Ficheros Java

Se muestra el main de la aplicación.

package com.jorgehernandezramirez.spring.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients("com.jorgehernandezramirez.spring.springcloud.feign")
public class Application {

    public Application(){
        //For Spring
    }

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

Para hacer uso de Ribbon y Feign es necesario definir dentro del contexto de Spring un objeto de la clase RestTemplate haciendo uso de la anotación @LoadBalanced para indicar que se va a hacer uso del descubrimiento de servicios.

package com.jorgehernandezramirez.spring.springcloud.configuration;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfiguration {

    @Bean
    @LoadBalanced
    RestTemplate loadBalancedRestTemplate() {
        return new RestTemplate();
    }
}

Mostramos la clase TestFeign que nos permitirá invocar a nuestras Apis a través de Feign.

package com.jorgehernandezramirez.spring.springcloud.feign;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@FeignClient("springcloudwebtest")
public interface TestFeign {

    @RequestMapping(value="/ping", method = RequestMethod.GET)
    String doAlive();
}

Mostramos la clase de test que permitirá invocar a las Apis haciendo uso de Ribbon y Feign apoyándose en Consul

package com.jorgehernandezramirez.spring.springcloud.controller;

import com.jorgehernandezramirez.spring.springcloud.feign.TestFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/ping")
public class TestController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private TestFeign testFeign;

    public TestController(){
        //For Spring
    }

    @RequestMapping
    public String doAlive() {
        return "Alive!";
    }

    @RequestMapping("/rest")
    public String doRestAlive() {
        return new RestTemplate().getForObject("http://localhost:8080/ping", String.class);
    }

    @RequestMapping("/rest/ribbon")
    public String doRestAliveUsingEurekaAndRibbon() {
        return restTemplate.getForObject("http://springcloudwebtest/ping", String.class);
    }

    @RequestMapping("/rest/feign")
    public String doRestAliveUsingFeign() {
        return testFeign.doAlive();
    }
}

Se muestra la clase PropertyController en donde se inyectará la propiedad property, que debe estar definida dentro de Consul, para exponer su valor a través de un controlador.

package com.jorgehernandezramirez.spring.springcloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/property")
public class PropertyController {

    @Value("${property}")
    private String property;

    public PropertyController(){
        //For Spring
    }

    @RequestMapping
    public String getProperty() {
        return property;
    }
}

5. Probando la aplicación

A continuación compilamos nuestros fuentes y arrancamos.

Compilamos

gradle clean build

Arrancamos la aplicación

java -jar [JAR_EN_BUILD_LIBS]

Para probar que desde RestTemplate se está haciendo uso del descubrimiento de servicios utilizando Consul, atacamos a la url http://localhost:8080/rest/ping/ribbon obteniendo

Alive!

Por otro lado para probar que Feign está haciendo uso del descubrimiento de servicios utilizando Consul, atacamos a la url http://localhost:8080/rest/ping/feign obteniendo

Alive!

Por último comprobamos que la propiedad definida dentro de Consul se ha inyectado correctamente. Para ello atacamos a la url http://localhost:8080/property obteniendo

property value