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 ?