1. Introducción

Bienvenidos al primer post sobre Spring Cloud de nuestro blog!.

Este será el primero de algunos posts que iremos publicando sobre esta tecnología y que hoy nos centraremos principalmente en el servidor de Eureka.

Spring Cloud es un conjunto de tecnologías desarrolladas por Netflix (Sí!, la plataforma online de series y películas) bajo el nombre Netflix OSS. Básicamente viene a dar respuesta a cómo desplegar y manejar nuestros microservicios de SpringBoot en un entorno cloud. Nos permite dar salida a algunas de las siguientes cuestiones:

  • Registro y descubrimiento de servicios
  • Balanceo de la carga
  • Enrutamiento
  • Control de ruptura de servicios
  • Configuración distribuída

En este primer post nos centraremos en el registro y descubrimiento de servicios así como en el balanceo de la carga.

Para entender la problemática, os pongo el siguiente supuesto. Imaginemos que tenemos 4 instancias de un mismo microservicio de gestión de usuarios los cuales exponen el método /users que nos da el listado de usuarios del sistema. Ahora imaginemos que tenemos, en nuestro entorno, otro microservicio que necesita consumir el controlador expuesto, sin saber a priori en qué máquinas ni qué puertos están desplegadas. Es aquí donde se hace necesaria la aparición de una entidad que lleve el recuento de las instancias de nuestros microservicios, así como su descubrimiento en caso de necesitarlo. Es aquí donde cobra sentido, el Eureka Server!.

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

2. Eureka server

Mostramos el build.gradle

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

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.1.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.springboot.eureka.Application"
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.springframework.cloud:spring-cloud-starter-eureka-server'
}

Arrancamos en el puerto 8761

server:
   port: 8761

Para habilitar el servidor de Eureka basta con utilizar la anotación @EnableEurekaServer

package com.jorgehernandezramirez.spring.springcloud.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class Application {

    public Application(){
        //For Spring
    }

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

Arrancamos el servidor de Eureka y nos vamos a http://localhost:8761 y voala!. Vemos como EUREKA se ha registrado a sí mismo.

3. Registrando un microservicio en Eureka

Nos creamos otro microservicio en el puerto 8080 que se registre en Eureka. Para ello necesitaremos:

  • Incluir dependencias del cliente de Eureka
  • Indicar en los properties donde está Eureka
  • Utilizar la anotación @EnableDiscoveryClient
  • Indicar el nombre con el que nos registraremos en Eureka

Utilización de la dependencia org.springframework.cloud:spring-cloud-starter-eureka que contiene el cliente que nos registrará en Eureka.

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-eureka'
}

Indicamos que queremos levantar nuestra aplicación en el puerto 8080. Después se indica donde se encuentra eureka y como queremos que se nos descubra (localhost:8080)

server:
   port: 8080

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  instance:
    hostname: localhost
    nonSecurePort: 8080

En el fichero bootstrap.yml es en donde indicamos el nombre con el que queremos que nuestro microservicio se registre en eureka.

spring:
  application:
     name: springcloudwebtest

Utilización de la anotación @EnableDiscoveryClient

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
public class Application {

    public Application(){
        //For Spring
    }

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

Una vez arrancada la aplicación, nos vamos de nuevo a http://localhost:8761 y vemos nuestro microservicio registrado.

4. Ribbon

Hasta ahora te preguntarás, y todo esto ¿para qué?. La respuesta está en lo que viene a continuación. ¿Te acuerdas del supuesto que hablamos en la introducción? Pues bien, lo que pretendemos hacer ahora es que podamos consumir un servicio sin conocer cuantas instancias del mismo existen, ni las máquinas en la que se encuentran, alojadas ni sus puertos. Para solventar este problema tenemos Ribbon, el cual se encargará de dos cosas:

  • Descubrimiento. Hacer el descubrimiento de las instancias, preguntandole a Eureka donde se encuentran
  • Balanceo. De todas las instancias encontradas seleccionar una

¿Cómo lo vamos a hacer?
Vamos a configurar la clase RestTemplate para que utilice Ribbon por debajo, de tal forma que atacaremos a nuestros servicios de la siguiente manera.

http://NOMBRE_MICROSERVICIO/path_to_controller

Nos creamos un bean de Spring de la clase RestTemplate utilizando la anotación @LoadBalanced. De esta mágica forma Ribbon ya actuará :).

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();
    }
}

Realmente tal magia no existe, lo que está pasando es que Spring por debajo está configurando un interceptor (RetryLoadBalancerInterceptor) para el objeto RestTemplate que está creando.

Nos creamos un controlador de prueba para testear nuestra aplicación

package com.jorgehernandezramirez.spring.springcloud.controller;

import com.jorgehernandezramirez.spring.springcloud.feign.TestFeign;
import com.netflix.discovery.converters.Auto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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;

    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);
    }
}

Llamamos al controlador ping

curl http://localhost:8080/ping

Nos devuelve

Alive!

Llamamos al controlador ping/rest que hace una llamada rest a http://localhost:8080/ping

http://localhost:8080/ping/rest

Nos devuelve

Alive!

Llamamos al controlador ping/rest/ribbon que hace una llamada rest a http:/SPRINGCLOUDWEBTEST/ping

http://localhost:8080/ping/rest/ribbon

Nos devuelve

Alive!

5. Feign

Visto Ribbon, ¿qué es Feign?. Pues es simple y llanamente una forma de consumir servicios rest de una forma fácil, rápida e indolora. Es exactamente lo mismo que utilizar RestTemplate pero de otra forma. Evidentemente utiliza Ribbon por debajo para hacer el descubrimiento de servicios y el balanceo correspondiente. Para configurarlo necesitaremos:

  • Incluir dependencia Feign
  • Crear interfaz Feign
  • Utilizar la anotación @FeignClients

Incluimos la dependencia

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

Vemos a continuación como crear un cliente feign. Destacar que necesitamos especificar el nombre del microservicio al que queremos atacar en la anotación @FeignClient

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();
}

Añadimos la anotación @EnableFeignClients. Además debemos indicar el paquete donde se encuentras nuestras interfaces feign.

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

Para utilizar nuestro cliente Feign lo inyectamos en nuestro controlador de pruebas y hacemos uso de él.

package com.jorgehernandezramirez.spring.springcloud.controller;

...

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

    @Autowired
    private TestFeign testFeign;

    ...

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

Desplegamos de nuevo y llamamos al controlador ping/rest/feign que hace una llamada rest a http:/SPRINGCLOUDWEBTEST/ping utilizando Ribbon para realizar el descubrimiento del servicio y el balanceo correspondiente.

http://localhost:8080/ping/rest/feign

Nos devuelve

Alive!