miércoles, 11 de agosto de 2021

Apache Camel: Service Discovery con Consul

Hoy vamos a ver como utilizar la funcionalidad de Service Discovery en un proyecto de Apache Camel, pero esta vez utilizando Consul de HashiCorp. Ya hemos visto esta misma funcionalidad pero con Eureka Netflix, aqui,. Por lo que este nuevo ejemplo no se diferenciará mucho.

Pero antes, averiguemos un poco más de Consul. A igual que Eureka es una herramienta que nos permite el registro de servicios de forma centralizada, permitiendo el descubrimiento de los mismos a otros clientes sin necesidad de conocer su dirección real, solo con un identificador. Pero además puede servirnos para almacenamiento de información o de la configuración a utilizar por los distintos clientes. Pero sobre este último apartado no veremos nada concreto en este ejemplo. 

Lo primero que vamos a hacer es levantar Consul. Para el caso de las pruebas podemos levantar una única instancia de Consul a través de un simple comando de docker. Pero nosotros lo haremos un poco más complejo, ya que Consul también puede ser levantado como un cluster de servidores, donde uno de ellos es el principal. Para llevar a cabo esta aproximación, crearemos un docker compose que nos permita levantar varias instancias de Consul y dejarlas activas para el ejemplo. Aquí abajo tenemos el docker compose y si accedemos a la URL http://localhost:8500 podremos acceder a su interfaz gráfica. 

#https://github.com/consul/consul/blob/master/docker-compose.yml
version: '3'

x-consul-container: &consul-container
  image: consul:latest
  networks:
    - consul-demo

x-consul-server: &consul-server
  <<: *consul-container
  command: "agent -server -retry-join consul-server-bootstrap -client 0.0.0.0"

services:
  consul-server-1:
    hostname: consult-server-1
    container_name: consult-server-1
    <<: *consul-server

  consul-server-bootstrap:
    hostname: consul-server-bootstrap
    container_name:   consul-server-bootstrap
    <<: *consul-agent
    ports:
      - "8400:8400"
      - "8500:8500"
      - "8600:8600"
      - "8600:8600/udp"
    command: "agent -server -bootstrap-expect 2 -ui -client 0.0.0.0"

networks:
  consul-demo:

El siguiente paso será crear un micro servicio con Apache Camel. Este micro servicio será muy básico y se conectará como cliente a Consul y registrará las distintas rutas en el. De esta forma y a través de un identificador, será consumibles por otros clientes del mismo servidor.  

Para ello, por un lado añadimos las siguientes dependencias, además de las normales para un micro servicio:

  • spring-boot-starter-actuator: Nos permite exponer rutas para la verificación del estado y los datos de la aplicación. 
  • camel-consul-starter: Nos permite preconfigurar Consul en nuestra aplicación.
Aparte indicaremos las siguientes propiedades en el fichero de configuración:
  • management.endpoints.web.exposure.include: Nos permite indicar rutas de monitorización a gestionar por Spring Actuator.
  • camel.cloud.consul.enabled: Habilitar el uso de Consul.
  • camel.cloud.consul.service-host: Indicar el servidor donde se encuentra ubicado Consul. Esta propiedad será la encargada de setear el tag service address al servicio en Consul. 
  • camel.cloud.consul.url: Para indicar la ruta completa donde se encuentra desplegado Consul.
Y ahora deberemos crear las rutas a registrar en el registro de Consul. Por cada una de las rutas, indicaremos cual será el identificador y otros meta datos asociados al servicio. Esto lo podremos hacer a través de la clase ServiceRegistrationRoutePolicy y el método routeProperty que nos permitirá indicar dichos datos. 

@Component
public class BookMockRouter extends RouteBuilder {
    private static Map<Integer, Book> books = new HashMap<>();
    static {
	books.put(1, new Book(1, "Dune", "Frank Herbert"));
	books.put(2, new Book(2, "The stars my destination", "Alfred Bester"));
	books.put(3, new Book(3, "Ender's game", "Orson S. Card"));
    }

    @Override
    public void configure() throws Exception {
	ServiceRegistrationRoutePolicy policy = new ServiceRegistrationRoutePolicy();

	rest().get("book").produces(MediaType.APPLICATION_JSON_VALUE).route()
		.routeId("mockClientGetAll")
		.routeProperty(ServiceDefinition.SERVICE_META_NAME, "bookGetAll")
		.routeProperty(ServiceDefinition.SERVICE_META_PORT, "9092")
		.routeProperty(ServiceDefinition.SERVICE_META_PATH, "api/book")
		.routePolicy(policy)
		.bean(BookMockRouter.class, "getAll(})").marshal().json();

	rest().get("book/{id}").produces(MediaType.APPLICATION_JSON_VALUE)
		.route().routeId("mockGetById")
		.routeProperty(ServiceDefinition.SERVICE_META_NAME, "bookGetById")
		.routeProperty(ServiceDefinition.SERVICE_META_PORT, "9092")
		.routeProperty(ServiceDefinition.SERVICE_META_PATH, "api/book/")
		.routePolicy(policy)
		.bean(BookMockRouter.class, "getById(${header.id})").marshal().json();
    }

    public Collection<Book> getAll() {
	return books.values();
    }
    public Book getById(final Integer id) {
	return books.get(id);
    }
}

Con este simple servicio podremos comprobar dos cosas. Primero que si invocamos la ruta http://localhost:9092/api/book, veremos el listado de libros asociados a la primera de las rutas. Y segundo, que si accedemos de nuevo a Consul, podremos ver como tenemos dos servicios más registrados. Cliqueando sobre los mismos podremos ver los detalles que hemos indicado en el código fuente. 

Para terminar el ejemplo y ver que funciona no solo la parte del registro de aplicaciones de forma dinámica en Consul. Sino que también funciona el descubrimiento de estos servicios, crearemos a continuación otro micro servicio con Apache Camel que hará uso del método serviceCall para invocar a Consul. 

Este nuevo servicio será incluso más sencillo, puesto que solo necesitará las dependencias para usar Consul en una aplicación de Apache Camel. Y en cuanto a propiedades, solo necesitará dos específicas:

  • camel.cloud.consul.enabled: Habilitar el uso de Consul.
  • camel.cloud.consul.service-discovery.url: Indicar la ruta donde se encuentra ubicado Consul.
Y ahora a crear la ruta para descubrir e invocar a nuestro servicio anterior:

@Component
public class BookServiceRouter extends RouteBuilder {
    @Override
    public void configure() throws Exception {
	rest("/serviceCall").get().produces(MediaType.APPLICATION_JSON_VALUE)
		.route().removeHeader(Exchange.HTTP_URI).serviceCall("bookGetAll")
		.convertBodyTo(String.class).log("Body: ${body}").end();

	rest("/serviceCall/{id}").get().produces(MediaType.APPLICATION_JSON_VALUE)
		.route().removeHeader(Exchange.HTTP_PATH)
		.setHeader(Exchange.HTTP_PATH, simple("${header.id}"))
		.removeHeader(Exchange.HTTP_URI).serviceCall("bookGetById")
		.convertBodyTo(String.class).log("Body: ${body}").end();
    }
}

Cada uno de los nuevos servicios, invoca a un servicio diferente a través del método serviceCall. Indicando el identificador del servicio a consultar, Consul nos generará la ruta asociada a dicho servicio y podremos invocarlo. Esa ruta se compone en base a los meta datos indicados en el otro micro servicio.  

Y con esto, hemos aprendido un poco más del funcionamiento de Apache Camel y otro magnifico Service Discovery. Como siempre, todo el código lo podéis ver aquí

No hay comentarios:

Publicar un comentario