:::: MENU ::::

Spring Cloud

Introducción

Spring Cloud es una serie de herramientas que nos permiten desarrollar sistemas distribuidos. Veremos cómo interconectar dos aplicaciones Web que se llaman la una a la otra, con un reconocimiento automático de las instancias y un balanceador de carga.

Aplicación hija

Simplemente muestra un mensaje por pantalla. Está hecha con Spring Boot, seleccionando las dependencias de Web y Cliente Eureka.

Necesitaremos trse componentes: un bean con el que transportar información, un controlador y una clase principal (además del fichero de configuración)

MyBean.java

package com.luisgomezcaballero.cloudchildservice;

public class MyBean {

	private String myString;
	private int port;

	public MyBean() {

	}

	public MyBean(String myString, int port) {
		super();
		this.myString = myString;
		this.port = port;
	}

	public String getMyString() {
		return myString;
	}

	public void setMyString(String myString) {
		this.myString = myString;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	@Override
	public String toString() {
		return "MyBean [myString=" + myString + ", port=" + port + "]";
	}

}

MyController.java

package com.luisgomezcaballero.cloudchildservice;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

	@Autowired
	private Environment environment;

	@RequestMapping(value = "/myuri", method = RequestMethod.GET)
	public MyBean mymethod() {

		MyBean myBean = new MyBean();
		
		myBean.setMyString("Hello from Child Service!");
		myBean.setPort(Integer.parseInt(environment.getProperty("local.server.port")));

		return myBean;
	}
}

CloudChildServiceApplication.java

package com.luisgomezcaballero.cloudchildservice;

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

@SpringBootApplication
@EnableEurekaClient
public class CloudChildServiceApplication {

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

application.properties

spring.application.name=cloudchildservice
server.port=8000
spring.jpa.show-sql=true
spring.h2.console.enabled=true
eureka.client.service-url.default-zone=http://localhost:8761/eureka

Por el momento no vamos a arrancar esta aplicación.

Aplicación madre

Ahora crearemos una aplicación que use esta aplicación hija. La idea es tener varias instancias de la hija para que se puedan ir alternando las llamadas entre todas ellas (a esto se le llama “balanceo” de carga).

Esta también es una aplicación Spring Boot con las dependencias Web, OpenFeign (como cliente de servicios Web), Netflix Ribbon (balanceador) y Cliente Eureka,.

Los componentes son parecidos a los de la aplicación anterior y, además, crearemos una interfaz que actuará como proxy (para consumir servicios Web), así como un fichero de configuración.

MyBean.java

package com.luisgomezcaballero.cloudparentservice;

public class MyBean {

	private String myString;
	private int port;

	public MyBean() {

	}

	public MyBean(String myString, int port) {
		super();
		this.myString = myString;
		this.port = port;
	}

	public String getMyString() {
		return myString;
	}

	public void setMyString(String myString) {
		this.myString = myString;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	@Override
	public String toString() {
		return "MyBean [myString=" + myString + ", port=" + port + "]";
	}

}

MyProxy.java

package com.luisgomezcaballero.cloudparentservice;

import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "cloudchildservice")
@RibbonClient(name = "cloudparentservice")
public interface MyProxy {

	@GetMapping("/myuri")
	public MyBean myProxyMethod();

}

MyController.java

package com.luisgomezcaballero.cloudparentservice;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private MyProxy proxy;

	@RequestMapping(value = "/myuri", method = RequestMethod.GET)
	public MyBean myMethod() {

		MyBean response = proxy.myProxyMethod();

		logger.info("{}", response);

		return response;
	}
}

CloudParentServiceApplication.java

package com.luisgomezcaballero.cloudparentservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients("com.luisgomezcaballero.cloudparentservice")
public class CloudParentServiceApplication {

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

application.properties

spring.application.name=cloudparentservice
server.port=8100
eureka.client.service-url.default-zone=http://localhost:8761/eureka

Por el momento tampoco vamos a arrancar esta aplicación.

Servidor Eureka

Una vez hechas las dos aplicaciones con los servicios Web, vamos a crear un servidor que será el que sea consciente de la existencia de las instancias anteiores y las coordine.

Es un proyecto Spring Boot con la dependencia Servidor Eureka.

No tiene otros componentes que la clase principal y el fichero de configuración.

CloudEurekaApplication.java

package com.luisgomezcaballero.cloudeureka;

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

@SpringBootApplication
@EnableEurekaServer
public class CloudEurekaApplication {

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

application.properties

spring.application.name=cloudeureka
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.default-zone=http://localhost:8761/eureka

Demostración de uso

Es importante respetar el orden de lanzamiento de las aplicaciones, y tener en cuenta que de la aplicación hija debemos tener varias instancias activas.

En primer lugar, lanzamos con Run As/Java Application el servidor Eureka. Para comprobar que se ha iniciado correctamente vamos a http://localhost:8761/.

A continuación, lanzamos de la misma manera la aplicación padre.

Finalmente, lanzamos también con Run As/Java Application la aplicación hija. Y ahora volemos a lanzar una segunda instancia pero esta vez en un puerto diferente.

Esto se consigue yendo a Run As/Run Configurations… y creando una configuración para esta app hija de la siguiente forma:
Creamos un nuevo elemento bajo el título “Java Application” con el mismo nombre que el proyecto hijo, y en la pestaña “Arguments”, en el apartado “VM Arguments” introducimos “-Dserver.port=8001” para que no se lance en el puerto por defecto, el 8000, sino en el 8001 (o en cualquier otro puerto que consideremos que no está ocupado).

Ahora y si no se ha producido ningún error, tendremos cuatro instancias levantadas.

Vamos a hacer una llamada a http://localhost:8100/myuri para que la aplicación madre llame a alguna de las instancias de las hijas (esto lo decide el servidor Eureka). Si sucesivamente volvemos a lanzar esta llamada veremos como se van llamando a todas ellas (podemos comprobarlo viendo cómo varía el puerto).

Repositorios

Estos proyectos pueden encontrarse en las siguientes localizaciones: https://github.com/luisgomezcaballero/cloudeureka, https://github.com/luisgomezcaballero/cloudparentservice y https://github.com/luisgomezcaballero/cloudchildservice.


So, what do you think ?