Microservicios políglotas en Spring Cloud con Spring Sidecar

1
2748

0. Índice de contenidos.


1. Introducción.

Spring Cloud proporciona el soporte necesario para exponer nuestros microservicios dentro de una nube de
microservicios, proporcionando una implementación de todos aquellos patrones de desarrollo y despliegue sobre
sistemas distribuidos que hacen que nuestro sistema sea escalable y reactivo.

Spring Cloud permite además desplegar microservicios desarrollados y desplegados bajo stacks tecnológicos diversos,
no tienen necesariamente que estar desarrollados bajo la pila de productos de Spring, Spring Boot y Spring Cloud.

Para ello podemos hacer uso de un proyecto de Spring Cloud denominado Spring Sidecar que nos permitirá disponer de
microservicios políglotas que se beneficien de todo el ecosistema de microservicios estructurales de Spring Cloud,
desarrollados en el lenguaje que mejor convenga a cada objetivo de negocio.

El patrón se denomina Sidecar por la metáfora de un sidecar acoplado a una motocicleta.
El microservicio que va a dar el soporte, funciones auxiliares, para engancharse a los servicios de infraestructuras
en la nube se acopla como un sidecar a una motocicleta que es el microservicio que realmente expone la lógica funcional.

2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS High Sierra 10.13.3
  • Oracle Java: 1.8.0_25
  • Spring Boot 1.5.9.RELEASE
  • Spring Cloud Edgware.RELEASE


3. Configuración del sidecar

Para el ejemplo generaremos dos proyectos, el sidecar y un proyecto en node.js que expondrá funcionalidad de negocio.

Para el sidecar podríamos tener un pom.xml como el siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<artifactId>tnt-ms-catalog-sidecar</artifactId>
	<packaging>jar</packaging>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.9.RELEASE</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Edgware.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
	
		<!-- spring boot -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
		<!-- spring cloud -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-netflix-sidecar</artifactId>
		</dependency>

		<!-- spring cloud monitoring -->
		<dependency>
		     <groupId>org.springframework.cloud</groupId>
		     <artifactId>spring-cloud-starter-sleuth</artifactId>
		</dependency>
		
		<dependency>
		    <groupId>org.springframework.cloud</groupId>
		    <artifactId>spring-cloud-starter-zipkin</artifactId>
		</dependency>

		<!-- admin -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		
	</dependencies>
</project>

Como con el resto de microservicios de infraestructura solo tenemos que añadir el soporte de los starters correspondientes
y una anotación @EnableSidecar, para habilitar el proyecto como Sidecar, en la clase anotada como @SpringBootApplication

package com.autentia.training.microservices.spring.cloud.catalog;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.sidecar.EnableSidecar;

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

A continuación, en el application.yml del mismo proyecto incluimos las siguientes propiedades

spring:
  application:
    name: ms-catalog
    
server:
  port: 8082

sidecar:
  port: ${port:3000}
  health-uri: http://${sidecar.host:localhost}:${sidecar.port}/${health-uri:health.json}
  home-page-uri: http://${sidecar.host:localhost}:${sidecar.port}/
  

Por un lado tenemos el puerto de nuestro Sidecar (8082) y por otro la configuración del microservicio al que lo vamos a
acoplar:

  • puerto: 3000
  • url de health check: http://localhost:3000/health.json que es lo único que obligatoriamente debe implementar el microservicio de negocio, para redirigir las peticiones de health check de eureka.
  • home del microservicio: http://localhost:3000


4. Un microservicio en node.js.

Vamos a generar el microservicio de negocio en node.js desplegado sobre un servidor express sin muchas más pretensiones
que devolver un json estático y exponer un end-point de health check que es lo único que nos vincula al sidecar.

Aún en node, vamos a configurar un pom.xml con el plugin de npm para que esté gestionado también por maven.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<artifactId>tnt-ms-catalog-js</artifactId>
	<packaging>jar</packaging>

	<build>
	<plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <executions>
                <execution>
                    <id>exec-npm-install</id>
                    <phase>generate-sources</phase>
                    <configuration>
                        <workingDirectory>${project.basedir}/src/main/resources</workingDirectory>
                        <executable>npm</executable>
                        <arguments>
                            <argument>install</argument>
                        </arguments>
                    </configuration>
                    <goals>
                        <goal>exec</goal>
                    </goals>
                </execution>
                <execution>
                    <id>exec-npm-run-tsc</id>
                    <phase>generate-sources</phase>
                    <configuration>
                        <workingDirectory>${project.basedir}/src/main/resources</workingDirectory>
                        <executable>npm</executable>
                        <arguments>
                            <argument>run</argument>
                        </arguments>
                    </configuration>
                    <goals>
                        <goal>exec</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
	</plugins>
	</build>
</project>

Y dentro del directorio de resources un package.json con el siguiente contenido:

{
  "name": "tnt-ms-catalog-js",
  "version": "0.0.1-SNAPSHOT",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon server.js"
  },
  "dependencies": {
    "body-parser": "^1.6.5",
    "compression": "^1.0.11",
    "cors": "^2.4.1",
    "dotenv": "^0.4.0",
    "errorhandler": "^1.1.1",
    "express": "^4.8.5",
    "morgan": "^1.2.3"
  },
  "devDependencies": {
    "nodemon": "^1.14.11"
  }
}

Un fichero estático products.json para devolver un listado de catálogo de productos:

[
{
code: 54,
ean: "8412866002982",
name: "Café de tueste natural",
description: "Envase 1000g"
},
{
code: 55,
ean: "8412866003019",
name: "Cacao",
description: "Envase 1000g"
},
{
code: 71,
ean: "8412866998414",
name: "Ebo Latte",
description: "El sabor tradicional y genuino de nuestro café 100% arábica. Perfecto para los más cafeteros. Ideal para consumir a 4*C. Sin colorantes ni aromas artificiales."
},
{
code: 79,
ean: "1841286600264",
name: "Ebo Latte sin lactosa",
description: "El sabor tradicional y genuino, rico y suave de nuestro café de siempre acompañado de leche sin lactosa 0% materia grasa. Café 100% arábica. Perfecto para los más cafeteros. Ideal para consumir a 4*C. Sin colorantes ni aromas artificiales. Sin azúcares añadidos. Con edulcorantes."
}
]

Y el fichero server.js de configuración del servidor express con un contenido como el siguiente:

var logger          = require('morgan'),
    cors            = require('cors'),
    http            = require('http'),
    express         = require('express'),
    errorhandler    = require('errorhandler'),
    dotenv          = require('dotenv'),
    bodyParser      = require('body-parser');

var app = express();


var products = require('./products.json');

dotenv.load();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors());

app.use(function(err, req, res, next) {
  if (err.name === 'StatusError') {
    res.send(err.status, err.message);
  } else {
    next(err);
  }
});

if (process.env.NODE_ENV === 'development') {
  app.use(logger('dev'));
  app.use(errorhandler())
}

app.get('/health.json', function(req, res) {
	res.status(200).json({"status": "UP"});
});

app.get('/products', function(req, res) {
	console.log('products from nodejs...');
	res.status(200).send(products);
});

var port = process.env.PORT || 3000;

http.createServer(app).listen(port, function (err) {
  console.log('listening in http://localhost:' + port);
});

Desde el punto de vista del ejemplo lo más relevante es el end-point de health check para el cuál debemos devolver un
200 con un mensaje

{"status": "UP"}


5. Referencias.


6. Conclusiones.

Los patrones Sidecar y Ambassador justificarían de algún modo disponer de microservicios en la nube que no exponen
directamente funcionalidad de negocio; para el resto, por favor, recordad que los microservicios deben estar «construidos
alrededor de capacidades de negocio».

Por último, sopesad también el coste de mantenimiento de esta poliglosía, desde el punto de vista de proyecto o de
producto, antes de recomendar o aventurarse en el desarrollo bajo distintos stacks tecnológicos bajo una ya costosa
arquitectura orientada a microservicios.

Un saludo.

Jose

1 COMENTARIO

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad