Microservicios. Una aproximación práctica con Spring.

Microservicios. Una aproximación básica.

Martin Fowler propone en su artículo “Microservices” una de las definiciones que más me gusta para describir qué son los microservicios:

La arquitectura basada en microservicios es una nueva manera de desarrollar aplicaciones como si fueran un conjunto de pequeños servicios, cada uno de ellos corriendo su propio proceso (servidor) y comunicándose entre si utilizando mecanismos de comunicación ligeros, normalmente protocolo HTTP.

Lo dice él y lo comparto yo, los microservicios son una nueva manera de diseñar aplicaciones que obliga a tener en cuenta el hecho de que cada funcionalidad es un servicio, cada uno con sus propios recursos, abandonando el consabido enfoque monolítico tradicional donde todas las funcionalidades forman parte de una único servicio.

Esta entrada abre una sucesión de artículos donde de manera básica quiero aproximarme al mundo de los microservicios utilizando diferentes aproximaciones; Spring Cloud, Play y vert.x.

Este primer artículo recorre el enfoque desde el punto de vista de Spring con Spring Cloud. Mi intención es implementar unos microservicios utilizando el ecosistema propuesto por Spring framework, a saber; Springfox, la implementación de Spring de la especificación Swagger para documentar la API REST, y Spring Cloud Netflix para ofrecer el microservicio y ponerlo a disposición de terceros a través del Service Registry de Netflix, Eureka.

El ejemplo.

El ejemplo elegido es una funcionalidad básica pero suficiente para permitir probar el desarrollo de microservicios a través de las soluciones elegidas, en esta entrada Spring Cloud.

La aplicación va a dar servicios a operaciones aritméticas ofreciendo dos microservicios, uno que realiza suma de dos operandos y el otro que realiza multiplicaciones de dos operandos.

De manera común a cada una de las pruebas voy a utilizar Apache Maven para manejar el ciclo de vida de cada componente que cree.

Spring Cloud y Spring Boot, dos facilitadores para el desarrollo.

Spring boot y Spring cloud son dos de las nuevas herramientas que Spring propone para facilitar el desarrollo de aplicaciones distribuidas basadas en microservicios.

Spring Boot es uno de los nuevos inventos de la familia Spring cuya pretensión es facilitar la vida a los desarrolladores simplificando todo el trabajo de configuración del propio framework. Para ello, Spring boot autoconfigura los componentes, automatizando la gestión de dependencias y el despliegue. Estas autoconfiguraciones son perfectas para los casos más comunes, pero en caso de necesitar algo más especial, las autoconfiguraciones pueden ser personalizadas utilizando anotaciones y/o ficheros de configuración.

Spring Cloud es otro de esos geniales inventos de la familia Spring que propone un ecosistema de herraminetas que nos ayudan a desplegar nuestros servicios en nubes. Soporta Cloud Foundry y Heroku de caja, así como otras nubes a través de conectores. Spring Cloud permitirá a las aplicaciones descubrir y compartir información/configuración de otras aplicaciones, y de sí misma, en tiempo de ejecución.

Servicios REST para la calculadora.

El primer paso para probar la tecnología de Spring es generar el servicio REST que va a proporcionar las operaciones aritméticas de suma y multiplicación de la calculadora.

Gracias a Spring Boot el desarrollo y pruebas de este API se hace muy rápidamente y con apenas esfuerzo.

El primer paso es crear un proyecto Java para contener el API REST. El proyecto se llama CalculadoraAritmeticaRestAPI.


4.0.0
com.tecnicomio.microservices.springfrwk
CalculadoraAritmeticaRestAPI
1.0-SNAPSHOT

org.springframework.boot
spring-boot-starter
1.5.10.RELEASE

org.springframework.boot
spring-boot-starter-web
1.5.10.RELEASE

Es importante fijarse que las únicas dependencias que he añadido han sido las referentes con spring boot. No he incluido ninguna dependencia referente al API REST. Ya se encargará Spring Boot de encontrar las dependencias necesarias para compilar y ejecutar correctamente el proyecto. Lo que si se ha añadido es el starter web para que Spring Boot tenga las dependencias necesarias para manejar aplicaciones web.

El siguiente paso es crear el servicio REST que proporciona las operaciones que se necesitan para la calculadora aritmética; sumar y multiplicar.

package com.tecnicomio.microservicios.spring;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/aritmetica")
public class CalculadoraAritmeticaRESTController {
public CalculadoraAritmeticaRESTController() {
}

@RequestMapping(
value = "/sumar",
method = RequestMethod.GET,
params = {"operando1", "operando2"}
)
public Integer sumar(@RequestParam("operando1") Integer operando1,
@RequestParam("operando2") Integer operando2) {
return operando1 + operando2;
}

@RequestMapping(
value = "/multiplicar",
method = RequestMethod.GET,
params = {"operando1", "operando2"}
)
public Integer multiplicar(@RequestParam("operando1") Integer operando1,
@RequestParam("operando2") Integer operando2) {
return operando1 * operando2;
}
}

No es el objetivo de esta entrada explicar cómo desarrollar servicios REST con Spring así que sólo comentaré que la principal anotación para convertir una clase Java en un API REST es @RestController. La otras anotaciones, leanse @RequestMapping y @RequestParam, sirven para configurar las funcionalidades que se exponen en el API. Ni que decir tiene que existen muchas más anotaciones y configuraciones, así que os dejo un link al tutorial de servicios REST de spring.io donde se explican bastante bien los conceptos:

Building a RESTful Web Service

Y finalmente, para que Spring Boot obre su magia se implementa una clase cuyo principal cometido es decirle a Spring que nos encontramos ante una aplicación web que contiene un servicio REST que debe publicarse para poder ser invocado.

package com.tecnicomio.microservicios.spring;

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

@SpringBootApplication(scanBasePackages = {"com.tecnicomio.microservicios.spring"})
public class CalculadoraAritmeticaServer {
public static void main(String[] args) {
SpringApplication.run(CalculadoraAritmeticaServer.class, args);
}
}

Podría haber incluido estas líneas directamente en el controller REST, pero he preferido por orden y limpieza crear esta mini clase como launcher.

Lo más curioso de Spring Boot es la inteligencia que incorpora, ya que al ejecutar estas 11 escasas líneas de código, Spring Boot sabe que se encuentra ante una aplicación web, con un servicio REST y arranca un Tomcat embebido para que se pueda acceder y ejecutar el servicio aritmético de calculadora.

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::       (v1.5.10.RELEASE)

2018-03-15 12:37:48.853  INFO 121776 --- [           main] c.t.m.s.CalculadoraAritmeticaServer      : Starting CalculadoraAritmeticaServer on devstation.localdomain with PID 121776 (/srv/java/src/pruebassimples/microservices/springfrwk/SumaAritmetica/target/classes started by developer in /srv/java/src/pruebassimples/microservices/springfrwk/SumaAritmetica)
2018-03-15 12:37:48.859  INFO 121776 --- [           main] c.t.m.s.CalculadoraAritmeticaServer      : No active profile set, falling back to default profiles: default
2018-03-15 12:37:48.975  INFO 121776 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@782859e: startup date [Thu Mar 15 12:37:48 CET 2018]; root of context hierarchy
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.cglib.core.ReflectUtils$1 (file:/home/developer/.m2/repository/org/springframework/spring-core/4.3.14.RELEASE/spring-core-4.3.14.RELEASE.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of org.springframework.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
2018-03-15 12:37:50.549  INFO 121776 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2018-03-15 12:37:50.563  INFO 121776 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-03-15 12:37:50.564  INFO 121776 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.27
2018-03-15 12:37:50.646  INFO 121776 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-03-15 12:37:50.646  INFO 121776 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1685 ms
2018-03-15 12:37:50.734  INFO 121776 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2018-03-15 12:37:50.737  INFO 121776 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-03-15 12:37:50.737  INFO 121776 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-03-15 12:37:50.737  INFO 121776 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-03-15 12:37:50.737  INFO 121776 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-03-15 12:37:51.154  INFO 121776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@782859e: startup date [Thu Mar 15 12:37:48 CET 2018]; root of context hierarchy
2018-03-15 12:37:51.218  INFO 121776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/aritmetica/multiplicar],methods=[GET],params=[operando1 && operando2]}" onto public java.lang.Integer com.tecnicomio.microservicios.spring.CalculadoraAritmeticaRESTController.multiplicar(java.lang.Integer,java.lang.Integer)
2018-03-15 12:37:51.219  INFO 121776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/aritmetica/sumar],methods=[GET],params=[operando1 && operando2]}" onto public java.lang.Integer com.tecnicomio.microservicios.spring.CalculadoraAritmeticaRESTController.sumar(java.lang.Integer,java.lang.Integer)
2018-03-15 12:37:51.221  INFO 121776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-03-15 12:37:51.221  INFO 121776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-03-15 12:37:51.244  INFO 121776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 12:37:51.244  INFO 121776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 12:37:51.274  INFO 121776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 12:37:51.423  INFO 121776 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-03-15 12:37:51.537  INFO 121776 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-03-15 12:37:51.542  INFO 121776 --- [           main] c.t.m.s.CalculadoraAritmeticaServer      : Started CalculadoraAritmeticaServer in 13.353 seconds (JVM running for 14.096)

Así que con un pom.xml y dos clases java muy simples tenemos el servicio REST publicado y accesible.

Ejecutando el Servicio REST con Postman

Documentando el API.

Para poder ejecutar los servicios expuestos debemos conocer la URL para invocarlo, los métodos que ofrece, los parámetros con que se llama a cada método y la información con la que se rellena.

Una manera de recibir esta información podría ser, por ejemplo, vía email, pero una mejor manera de recibirlo es tener un portal donde se pueda acceder a un listado de todos los servicios REST publicados y su información de uso. Este portal que concentra la información sobre APIs, es el objeto de la especificación Swagger.

La especificación swagger define un interfaz estándar para las APIs RESTful, que no se casa con ningún lenguaje de programación concreto, qué permite tanto a humanos como a máquinas descubrir y comprender las capacidades del servicio sin necesitad de acceder, ni al código fuente del mismo, ni a su documentación, ni a través de la inspección del comportamiento del servicio a través de la red. Cuando esta interfaz se define correctamente, los posibles consumidores del API pueden entender e interactuar correctamente con el servicio.

Springfox es la implementación de la especificación swagger que propone Spring. Para añadirla en nuestro servicio aritmético tenemos que seguir varios pasos.

El primero, añadir en el pom.xml la dependencia necesaria para utilizar Springfox.


io.springfox
springfox-swagger2
2.8.0

io.springfox
springfox-swagger-ui
2.8.0

El segundo es crear una clase de configuración de swagger.

package com.tecnicomio.microservicios.spring.com.tecnicomio.microservicios.spring.swagger;

import com.tecnicomio.microservicios.spring.CalculadoraAritmeticaRESTController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@ComponentScan(basePackageClasses = {CalculadoraAritmeticaRESTController.class})
public class SwaggerConfiguration {

@Bean
public Docket calculadoraAritmeticaApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("api-calculadora-aritmetica")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/aritmetica.*"))
.build();
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Calculadora aritmética API Rest")
.description("API RESTful para la calculadora aritmética.")
.termsOfServiceUrl("http://en.wikipedia.org/wiki/Terms_of_service")
.contact(new Contact("Eduardo González de Viñaspre", "http://tecnicomio.com", "eduardo.gonzalezdevinaspre@gmail.com"))
.license("Apache License Version 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.version("2.0")
.build();
}
}

Importante la anotación @EnableSwagger2 que indica que hay que activar swagger. El método calculadoraAritmeticaApi indica el formato de la documentación y su nombre, y el método apiInfo que indica información básica sobre la documentación que nos vamos a encontrar en el portal publicado.

Y finalmente documentar el controlador REST del API así como los POJOs que usemos para definir los parámetros de entrada y de salida de los diferentes métodos. En este caso como no hay POJOs, solo documento el controlador REST.

package com.tecnicomio.microservicios.spring;

import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.*;

import javax.ws.rs.core.MediaType;

@RestController
@RequestMapping("/aritmetica")
@Api(value = "/aritmetica",
description = "Proporciona las operaciones necesarias para implementar una calculadora aritmética",
produces = "text/plain")
public class CalculadoraAritmeticaRESTController {
public CalculadoraAritmeticaRESTController() {
}

@RequestMapping(
value = "/sumar",
method = RequestMethod.GET,
params = {"operando1", "operando2"},
produces = MediaType.TEXT_PLAIN
)
@ApiOperation(value="Suma aritmética", notes = "Realiza la suma de dos operandos")
@ApiResponses( {
@ApiResponse(code = 200, message = "Devuelve el resultado de la suma.")
})
public Integer sumar(@RequestParam(value = "operando1",defaultValue = "0") @ApiParam(value = "operando 1 de la suma", defaultValue = "0", required = true, example = "5") Integer operando1,
@RequestParam(value = "operando2", defaultValue = "0") @ApiParam(value = "operando 2 de la suma", defaultValue = "0", required = true, example = "15") Integer operando2) {
return operando1 + operando2;
}

@RequestMapping(
value = "/multiplicar",
method = RequestMethod.GET,
params = {"operando1", "operando2"}
)
@ApiOperation(value="Multiplicación aritmética", notes = "Realiza la multiplicación de dos operandos")
@ApiResponses( {
@ApiResponse(code = 200, message = "Devuelve el resultado de la multiplicación.")
})
public Integer multiplicar(@RequestParam(value = "operando1", defaultValue = "1") @ApiParam(value = "operando 1 de la multiplicación", defaultValue = "1", required = true, example = "5") Integer operando1,
@RequestParam(value = "operando2", defaultValue = "1") @ApiParam(value = "operando 2 de la multiplicación", defaultValue = "1", required = true, example = "10") Integer operando2) {
return operando1 * operando2;
}
}

Las anotaciones que obran la magia son:

  • @Api – Define un contenedor de documentación.
  • @ApiOperation – Define la documentación de una operación dentro del servicio.
  • @ApiResponses y @ApiResponse – Documenta las respuestas del método del servicio.
  • @ApiParam- Documenta los parámetros de los parámetros de los métodos.

Gracias a Spring Boot no se necesita mucho más. Ahora ya se puede acceder a la URL de swagger: http://localhost:8080/swagger-ui.html# y si todo ha salido bien veremos la página que swagger ofrece con la documentación del servicio REST.

Documentación swagger para el proyecto de calculadora aritmética

Si extendemos una operación, además de ver la documentación, podemos incluso llegar a probarla:

Formulario Swagger para probar métodos.

Arrancando una herramienta de Service Discovery: Eureka.

Al final la tecnología de microservicios está formada por servicios simples como el servicio de aritmética que acabamos de publicar, pero que además quedan envueltos por una ecosistema que aporta una serie de ventajas que facilitan el mantenimiento de estos servicios, por ejemplo, el registro y localización de los mismos, el balanceo de carga, la tolerancia a fallos, entre otros.

El registro de los servicios para su posterior localización y uso es importantísimo en cuanto tenemos un proyecto medio-grande entre manos. Basta con imaginar tener 100 microservicios dando servicio a un sitio web para darnos cuenta que tenemos que tener algún tipo de inventario con información sobre ellos. En el ecosistema de los microservicios la pieza que facilita este registro es el Service Discovery.

A día de hoy existen varios Service Discovery, algunos de los más importantes son:

  • Zookeeper de Apache Foundation
  • Eureka de Netflix
  • Consul

El siguiente paso por tanto es registrar el servicio aritmético en un Service Discovery, y para ello voy a utilizar Eureka, el Service Discovery que utiliza Netflix, una de las empresas que sirve más peticiones a lo largo y ancho de internet.

Lo primero que voy a hacer es crear un nuevo proyecto EurekaApplication para contener todo lo relativo a la ejecución de Eureka. El pom.xml del nuevo proyecto es:


4.0.0
com.tecnicomio.microservices.springfrwk
EurekaServer
1.0-SNAPSHOT

org.springframework.cloud
spring-cloud-starter-eureka-server
1.4.3.RELEASE

org.springframework.boot
spring-boot-starter
1.5.10.RELEASE

javax.xml.bind
jaxb-api
2.3.0

org.springframework.cloud
spring-cloud-starter-parent
Brixton.SR7
pom
import

¡¡Cuidado!! He tenido que añadir las dependencias de jaxb-api para que el Tomcat que Spring Boot incluye arranque correctamente. Esto es debido a que en la JDK 9 tras implementar los Java Modules, algunas librerías ya no se incluyen como si se hacía en la JDK 6/7/8. Uno de esos paquetes que no se incluye es java.xml.bind que contiene todo el funcionamiento de JAXB.

A continuación añado una clase para arrancar el servidor de Eureka.

package com.tecnicomio.microservicios.spring;

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

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

La anotación aquí es @EnableEurekaServer que le dice a Spring Boot que se quiere arrancar el Service Discovery de Eureka.

Y finalmente, para que configurar brevemente el servidor Eureka, se añade el fichero application.yml al directorio resources del proyecto.

server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false

En el fichero de configuración estamos indicando que el servidor de Eureka se arranque en el puerto 8761 (el puerto por defecto de los servidores de Eureka), que el cliente no se registre a si mismo ya que vamos a registrar el servicio aritmético.

Así que finalmente, con estos pasos, ya deberíamos tener el servidor Eureka arrancado.

Eureka Server Up and running

Con esto hemos conseguido que el servicio se autoregistre en Eureka, que le notifique cuando está disponible, cómo se le puede invocar, los metadatos que se pueden utilizar y mucha más información.

Descubriendo el servicio aritmético en Eureka.

Recapitulando, llegados a este punto hemos creado un servicio REST que ofrece funcionalidad para una calculadora aritmética, y hemos arrancado una instancia de Eureka, una herramienta para el descubrimiento de servicios.

Desafortunadamente, cuando miramos la consola de Eureka todavía no se ve registrado el servicio aritmético. Esto es debido a que hay que indicarle al servicio que implemente el Service Registry pattern de Eureka para convertirle en un posible cliente.

El primer paso es añadir las depedencias de Eureka al proyecto CalculadoraAritmeticaRestAPI:


org.springframework.cloud
spring-cloud-starter-eureka-server
1.4.3.RELEASE

El segundo indicarle al servicio aritmético que es un cliente de Eureka. Para ello añadimos la anotación @EnableEurekaClient a la clase servidor.

import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@RestController
@RequestMapping("/aritmetica")
public class CalculadoraAritmeticaRESTController {

Y por último, configurar el cliente Eureka mediante un fichero application.yml donde indiquemos el nombre del micro-servicio, la URL y otras posibles configuraciones.

spring:
application:
name: calculadora-aritmetica-eureka-client
server:
port: 0
eureka:
client:
serviceUrl:
defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
instance:
preferIpAddress: true

Hecho esto, se arranca el servicio CalculadoraAritmetica y automáticamente debería registrarse en el servidor Eureka indicado en el fichero YAML de configuración.

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::        (v1.3.8.RELEASE)

2018-03-15 15:25:41.087  INFO 7659 --- [           main] c.t.m.s.CalculadoraAritmeticaServer      : No active profile set, falling back to default profiles: default
2018-03-15 15:25:41.105  INFO 7659 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5942ee04: startup date [Thu Mar 15 15:25:41 CET 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@43b9fd5
2018-03-15 15:25:42.095  WARN 7659 --- [           main] o.s.c.a.ConfigurationClassPostProcessor  : Cannot enhance @Configuration bean definition 'refreshScope' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'.
2018-03-15 15:25:42.259  INFO 7659 --- [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=357c0c59-b5b5-38af-babc-0373b7d67e30
2018-03-15 15:25:42.275  INFO 7659 --- [           main] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-03-15 15:25:42.328  INFO 7659 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [class org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$defd02e1] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-03-15 15:25:42.335  INFO 7659 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration' of type [class org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration$$EnhancerBySpringCGLIB$$885c04d7] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-03-15 15:25:42.710  INFO 7659 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 0 (http)
2018-03-15 15:25:42.728  INFO 7659 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2018-03-15 15:25:42.729  INFO 7659 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.37
2018-03-15 15:25:42.850  INFO 7659 --- [ost-startStop-1] org.apache.catalina.loader.WebappLoader  : Unknown loader jdk.internal.loader.ClassLoaders$AppClassLoader@1b9e1916 class jdk.internal.loader.ClassLoaders$AppClassLoader
2018-03-15 15:25:42.868  INFO 7659 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-03-15 15:25:42.868  INFO 7659 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1764 ms
2018-03-15 15:25:43.278  INFO 7659 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2018-03-15 15:25:43.282  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'metricsFilter' to: [/*]
2018-03-15 15:25:43.282  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-03-15 15:25:43.282  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-03-15 15:25:43.282  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-03-15 15:25:43.283  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'requestContextFilter' to: [/*]
2018-03-15 15:25:43.283  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'webRequestLoggingFilter' to: [/*]
2018-03-15 15:25:43.283  INFO 7659 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'applicationContextIdFilter' to: [/*]
2018-03-15 15:25:44.388  INFO 7659 --- [           main] o.s.cloud.commons.util.InetUtils         : Cannot determine local hostname
2018-03-15 15:25:44.737  INFO 7659 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5942ee04: startup date [Thu Mar 15 15:25:41 CET 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@43b9fd5
2018-03-15 15:25:44.866  INFO 7659 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/aritmetica/sumar],methods=[GET],params=[operando1 && operando2]}" onto public java.lang.Integer com.tecnicomio.microservicios.spring.CalculadoraAritmeticaRESTController.sumar(java.lang.Integer,java.lang.Integer)
2018-03-15 15:25:44.867  INFO 7659 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/aritmetica/multiplicar],methods=[GET],params=[operando1 && operando2]}" onto public java.lang.Integer com.tecnicomio.microservicios.spring.CalculadoraAritmeticaRESTController.multiplicar(java.lang.Integer,java.lang.Integer)
2018-03-15 15:25:44.869  INFO 7659 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-03-15 15:25:44.869  INFO 7659 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-03-15 15:25:44.897  INFO 7659 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 15:25:44.897  INFO 7659 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 15:25:44.941  INFO 7659 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-15 15:25:45.415  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/pause || /pause.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
2018-03-15 15:25:45.416  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/beans || /beans.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.417  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.value(java.util.Map)
2018-03-15 15:25:45.417  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env/reset],methods=[POST]}" onto public java.util.Map org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.reset()
2018-03-15 15:25:45.420  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/trace || /trace.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.421  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/dump || /dump.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.422  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/mappings || /mappings.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.423  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/restart || /restart.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.context.restart.RestartMvcEndpoint.invoke()
2018-03-15 15:25:45.424  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/features || /features.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.424  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
2018-03-15 15:25:45.425  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env || /env.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.425  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/configprops || /configprops.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.426  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/archaius || /archaius.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.427  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/health || /health.json],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(java.security.Principal)
2018-03-15 15:25:45.427  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/info || /info.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.428  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/refresh || /refresh.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
2018-03-15 15:25:45.429  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
2018-03-15 15:25:45.429  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics || /metrics.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.430  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/resume || /resume.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
2018-03-15 15:25:45.431  INFO 7659 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/autoconfig || /autoconfig.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-15 15:25:45.554  INFO 7659 --- [           main] o.s.ui.freemarker.SpringTemplateLoader   : SpringTemplateLoader for FreeMarker: using resource loader [org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5942ee04: startup date [Thu Mar 15 15:25:41 CET 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@43b9fd5] and template loader path [classpath:/templates/]
2018-03-15 15:25:45.555  INFO 7659 --- [           main] o.s.w.s.v.f.FreeMarkerConfigurer         : ClassTemplateLoader for Spring macros added to FreeMarker configuration
2018-03-15 15:25:45.634  WARN 7659 --- [           main] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.
2018-03-15 15:25:45.634  INFO 7659 --- [           main] c.n.c.sources.URLConfigurationSource     : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2018-03-15 15:25:45.642  WARN 7659 --- [           main] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.
2018-03-15 15:25:45.642  INFO 7659 --- [           main] c.n.c.sources.URLConfigurationSource     : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2018-03-15 15:25:45.689  WARN 7659 --- [           main] arterDeprecationWarningAutoConfiguration : spring-cloud-starter-eureka-server is deprecated as of Spring Cloud Netflix 1.4.0, please migrate to spring-cloud-starter-netflix-eureka-server
2018-03-15 15:25:45.768  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-03-15 15:25:45.784  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'refreshScope' has been autodetected for JMX exposure
2018-03-15 15:25:45.784  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'environmentManager' has been autodetected for JMX exposure
2018-03-15 15:25:45.786  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'configurationPropertiesRebinder' has been autodetected for JMX exposure
2018-03-15 15:25:45.787  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'refreshEndpoint' has been autodetected for JMX exposure
2018-03-15 15:25:45.788  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'restartEndpoint' has been autodetected for JMX exposure
2018-03-15 15:25:45.794  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'environmentManager': registering with JMX server as MBean [org.springframework.cloud.context.environment:name=environmentManager,type=EnvironmentManager]
2018-03-15 15:25:45.816  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'restartEndpoint': registering with JMX server as MBean [org.springframework.cloud.context.restart:name=restartEndpoint,type=RestartEndpoint]
2018-03-15 15:25:45.827  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'refreshScope': registering with JMX server as MBean [org.springframework.cloud.context.scope.refresh:name=refreshScope,type=RefreshScope]
2018-03-15 15:25:45.832  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'configurationPropertiesRebinder': registering with JMX server as MBean [org.springframework.cloud.context.properties:name=configurationPropertiesRebinder,context=5942ee04,type=ConfigurationPropertiesRebinder]
2018-03-15 15:25:45.848  INFO 7659 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'refreshEndpoint': registering with JMX server as MBean [org.springframework.cloud.endpoint:name=refreshEndpoint,type=RefreshEndpoint]
2018-03-15 15:25:46.013  INFO 7659 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2018-03-15 15:25:46.158  INFO 7659 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 42809 (http)
2018-03-15 15:25:46.158  INFO 7659 --- [           main] c.n.e.EurekaDiscoveryClientConfiguration : Updating port to 42809
2018-03-15 15:25:46.168  INFO 7659 --- [           main] o.s.c.n.eureka.InstanceInfoFactory       : Setting initial instance status as: STARTING
2018-03-15 15:25:46.741  INFO 7659 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
2018-03-15 15:25:46.743  INFO 7659 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
2018-03-15 15:25:46.877  INFO 7659 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML encoding codec XStreamXml
2018-03-15 15:25:46.878  INFO 7659 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML decoding codec XStreamXml
2018-03-15 15:25:47.112  INFO 7659 --- [           main] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Disable delta property : false
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Single vip registry refresh property : null
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Force full registry fetch : false
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Application is null : false
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Registered Applications size is zero : true
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Application version is -1: true
2018-03-15 15:25:47.140  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Getting all instance registry info from the eureka server
2018-03-15 15:25:47.657  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : The response status is 200
2018-03-15 15:25:47.658  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Starting heartbeat executor: renew interval is: 30
2018-03-15 15:25:47.667  INFO 7659 --- [           main] c.n.discovery.InstanceInfoReplicator     : InstanceInfoReplicator onDemand update allowed rate per min is 4
2018-03-15 15:25:47.670  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Discovery Client initialized at timestamp 1521123947669 with initial instances count: 0
2018-03-15 15:25:47.696  INFO 7659 --- [           main] c.n.e.EurekaDiscoveryClientConfiguration : Registering application calculadora-aritmetica-eureka-client with eureka with status UP
2018-03-15 15:25:47.697  INFO 7659 --- [           main] com.netflix.discovery.DiscoveryClient    : Saw local status change event StatusChangeEvent [timestamp=1521123947697, current=UP, previous=STARTING]
2018-03-15 15:25:47.715  INFO 7659 --- [           main] c.t.m.s.CalculadoraAritmeticaServer      : Started CalculadoraAritmeticaServer in 11.699 seconds (JVM running for 12.527)
2018-03-15 15:25:47.716  INFO 7659 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_CALCULADORA-ARITMETICA-EUREKA-CLIENT/localhost:calculadora-aritmetica-eureka-client:0: registering service...
2018-03-15 15:25:47.867  INFO 7659 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_CALCULADORA-ARITMETICA-EUREKA-CLIENT/localhost:calculadora-aritmetica-eureka-client:0 - registration status: 204
2018-03-15 15:26:14.100  INFO 7659 --- [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-03-15 15:26:14.100  INFO 7659 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-03-15 15:26:14.129  INFO 7659 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 29 ms
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Disable delta property : false
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Single vip registry refresh property : null
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Force full registry fetch : false
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Application is null : false
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Registered Applications size is zero : true
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Application version is -1: false
2018-03-15 15:26:17.659  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : Getting all instance registry info from the eureka server
2018-03-15 15:26:17.709  INFO 7659 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient    : The response status is 200
Eureka Client Published at Eureka Server

Estableciendo un Gateway API para dar acceso a los servicios

Llegados a este punto tenemos:

  • Un servicio REST para operaciones aritméticas
  • El servicios REST de operaciones aritméticas documentado con Swagger, y está documentación accesible a través de una URL
  • Eureka, el Registry Discovery de NETFLIX arrancado
  • El servicio REST de operaciones aritméticas capaz de ser detectado por el Registry Discovery.

La ultima pieza que voy a incluir en este ecosistema de microservicios es el Gateway que concentra las llamadas de los clientes. La verdad es que el ejemplo que estoy utilizando se queda un poco corto para entender porque es necesario un gateway, total, solo tenemos un servicio publicado en una máquina al que podemos acceder desde una única URL.

Pero, ¿qué pasaría si tuviera 10 servicios cada uno con su URL propia? Tendría 10 urls, cada una diferente y cada una con su documentación swagger. El API Gateway viene al rescate y ofrece un único punto de acceso, una URL de invocación homogénea y una única documentación swagger.

El API Gateway en el que voy a integrar mi API de operaciones aritméticas va a ser ZUUL, el API Gateway de Netflix.

Links

Swagger2
Minientrada

Breve introducción a las expresiones lambda.

Expresiones lambda

Las expresiones lambda no son complejas de usar y entre otras ventajas permiten pasar comportamiento como valor, algo muy utilizado en Javascript, y abreviar líneas de código sin perder legibilidad en el código. Es por esto que Java las adoptó definitivamente en la JavaSE 8. Pero, para que las expresiones lambda sean verdaderamente útiles, hay que llegar a entenderlas. Hasta que eso ocurre pueden llegar a ser bastante confusas.

Si nos atenemos a la definición oficial de expresión lambda diríamos que son funciones anónimas que implementan una interfaz funcional, que así dicho suena muy técnico, pero si te lo dicen así sin más, puede dejarte algo frío. A mi es lo que me pasó, así que me puse manos a la obra para intentar aterrizar el concepto y desgranar la definición teórica haciéndola comprensible para mortales como yo. De ese germen surgió esta entrada.

Lo que me quedó claro de la definición es qué, para entender las expresiones lambda hay que entender qué es una función anónima y que es una interfaz funcional.

Función anónima

Las funciones anónimas permiten crear instancias de un objeto que implementa un interfaz en particular sin que para ello sea necesario desarrollar la clase que implementa esa interfaz explícitamente.

¡Toma ya!

Esto se ve claro con un ejemplo. Imaginemos una interfaz que ofrece métodos para una calculadora:

package com.tecnicomio.pruebas.funcionesanonimas;

public interface ICalculadora {

    Integer suma (Integer operando1, Integer operando2);

}

De manera tradicional tendríamos una clase que implementara la interfaz:

package com.tecnicomio.pruebas.funcionesanonimas;

public class Calculadora implements ICalculadora {

    @Override
    public Integer suma(Integer operando1, Integer operando2) {
        return operando1 + operando2;
    }
}

Posiblemente, utilizaríamos esta clase en otra parte del código de la siguiente manera:

[...]
       ICalculadora calculadora = new Calculadora();
       Integer operando1 = 10;
       Integer operando2 = 22;
       Integer resultado = calculadora.suma(operando1, operando2);
       System.out.println(operando1 + "+" + operando2 + "=" + resultado);
[...]

Ahora haré una aproximación con funciones anónimas. La interfaz se mantiene, pero la clase desaparece.

El código resultado sería:

[...]

       Integer operando1 = 10;
       Integer operando2 = 22;

       System.out.println(operando1 + "+" + operando2 + "=" +
                new ICalculadora() {
                    @Override
                    public Integer suma(Integer operando1, Integer operando2) {
                        return operando1 + operando2;
                    }
                }.suma(operando1, operando2));
[...]

Repasando qué hemos conseguido con la función anónima, llegamos a las siguientes conclusiones:

  • Nos hemos ahorrado la definición y creación de la clase, que en el caso de clases que se usan en un único sitio, puede ser un gran ahorro. Así evitamos contaminar el proyecto con esas clases que no se reutilizan en otras partes.
  • Cuando la implementación es corta, como en el caso de este ejemplo, en el que el código se encuentre directamente en el lugar donde se usa, puede hacer que éste sea más entendible (aunque tal vez menos legible).
  • Permite seguir manejando correctamente las variables locales y miembros de la clase sin tener que definir un constructor para poder recibirlas y usarlas.

En este ejemplo, es probable que el ahorro de crear la clase, versus la disminución de legibilidad, haga que no merezca suficientemente la pena, pero pongamos un ejemplo donde verdaderamente las funciones anónimas dan la talla. Imaginemos una ventana con 15 botones y sus respectivos 15 event listeners cada vez que el usuario hace click en ellos. En este caso, habría que crear 15 clases donde cada una implementa el interfaz ActionListener. Clases que solo se usan en el punto del código donde se implementa el formulario. En este caso, usar funciones anónimas es mucho más rentable.

Interfaz Funcional

Un interfaz funcional es aquel interfaz que tiene únicamente un método y este método es abstracto, es decir un método sin implementar. Las interfaces funcionales fueron agregadas a partir de la versión JavaSE 8 y vienen de la mano de las expresiones lambda.

A continuación dos ejemplos de interfaz funcional. El primero es un ejemplo al uso, con un único método abstracto.

package com.tecnicomio.interfazfuncional;

public interface IInterfazFuncionalSimple {
  public String saludo(String nombre);
}

El segundo es un ejemplo un poco más rebuscado pero igualmente válido; un único método abstracto y varios métodos default.

package com.tecnicomio.interfazfuncional;

public interface IInterfazFuncionalRebuscada {
  public String saludo(String nombre);

  public default String holaMundo() {
    Return "Hola mundo.";
  }
}

Para asegurarnos que la interfaz cumple con las reglas de las interfaces funcionales podríamos anotar la interfaz con @FunctionalInterface. En este caso si introdujéramos más de un método abstracto (sin implementación) el compilador nos daría el error: «Multiple non overriding abstract methods found in interface com.tecnicomio.pruebas.interfazfuncional.InterfazFuncionalAnotada». En caso de no introducir ningún método abstracto, nos daría el error: «No Target method found».

package com.tecnicomio.interfazfuncional;

@FunctionalInterface
public interface IInterfazFuncionalAnotada {
  public String saludo(String nombre);

  public default String holaMundo() {
    Return "Hola mundo.";
  }
}

En el ejemplo de la calculadora, la interfaz ICalculadora es un claro ejemplo de Interfaz Funcional, sin anotar.

Expresiones lambda

Y ahora que ya han quedado un poco más claros los conceptos «funciones anónimas» e «interfaces funcionales» volvamos a la definición de expresión lambda: las expresiones lambda son funciones anónimas que implementan una interfaz funcional.

Por tanto, podríamos decir que son una evolución de las funciones anónimas que pretenden simplificar aún más el código, pero que para conseguir esta simplificación, obligan a que la funcionalidad de la expresión lambda implemente una interfaz funcional.

Sintaxis de las expresiones lambda

La sintaxis de las expresiones lambda es:

	
(Parametros) -> { cuerpo expresión lambda }

Teniendo en cuenta qué:

  • El operador lambda (->) separa la declaración de parámetros del cuerpo de la función.
  • Parámetros
    • Cuando se tiene un solo parámetro pueden omitirse los paréntesis.
    • Cuando no se tienen parámetros, o cuando se tienen dos o más, sí es necesario su uso.
  • Cuerpo de la expresión lambda
    • Cuando el cuerpo de la expresión lambda tiene una única línea pueden omitirse las llaves y no se necesita especificar la cláusula return en el caso de que se devuelva valor.

Ejemplos de expresiones lambda pueden ser:

  • Expresión lambda con un único parámetro y una única línea: num -> num+10
  • Expresión lambda sin parámetros y una única línea: () -> System.out.println(«Hola mundo»)
  • Expresión lambda con dos parámetros y una única línea: (int operando1, int operando2) -> operando1 * operando2
  • Expresión lambda con múltiples parámetros y varias líneas de función: (String nombre) -> {String retorno=»Hola «; retorno=retorno.concat(nombre); return retorno;}

La calculadora con expresiones lambda

Y como lo mejor para entender algo es verlo con un ejemplo, apliquemos las expresiones lambda al ejemplo de la calculadora que podría quedar así:

        ICalculadora calculadora = (Integer operando1, Integer operando2)->(operando1+operando2);

        Integer operando1 = 10;
        Integer operando2 = 22;

        System.out.println(operando1 + "+" + operando2 + "=" + calculadora.suma(operando1,operando2));

O si queremos ahorrar aún más líneas de código sin perder legibilidad podría quedar de esta otra forma:

    System.out.println("En una línea:10+22=" + ((ICalculadora)(Integer operando1, Integer operando2)->(operando1+operando2)).suma(10,22));

Conclusiones

Como se puede ver en el ejemplo de la calculadora utilizar expresiones lambda tienen claros y algunos obscuros.

Para mí los claros son:

  • Al hacer uso de funciones anónimas sin necesidad de crear clases anónimas se crea código más claro y conciso.
  • Acercan Java a la programación funcional muy utilizado en lenguajes de script, como Javascript, donde las funciones juegan un papel protagonista. Esto permite poder pasar funciones como valores de variables, valores de retorno o parámetros de otras funciones, es decir, gracias a las expresiones lambda se puede pasar comportamiento como valor.
  • Al usarlas en combinación con la API Stream se pueden realizar operaciones de tipo filtro/mapeo sobre colecciones de datos de forma secuencial o paralela siendo la implementación transparente al desarrollador.
  • Logran un código más compacto y fácil de leer.
  • Reducen la escritura de código.

El oscuro para mí es claro:

  • Hay que entenderlas para sacarles el máximo partido. Y entenderlas es cambiar la manera de pensar del javero de toda la vida.

Links