Integración de servicios y sistemas con Apache Camel

Escrito por picodotdev el .
java planeta-codigo
Enlace permanente Comentarios

Apache Camel es una librería específica para realizar tareas de integración que ya proporciona e implementa múltiples protocolos de comunicación, formatos de datos, componentes y patrones de integración. Ya tiene implementada toda esta funcionalidad que no hay que implementar en el caso de una aplicación con código propio. Al ser una librería es posible integrarlo en cualquier tipo de aplicación, en el artículo se muestra cómo utilizarlo en una aplicación de Spring Boot en un ejemplo.

Apache Camel

Java

Las empresas y organizaciones con cierta cantidad de años de vida con mucha probabilidad tiene una gran cantidad de servicios y sistemas con diferentes tecnologías, protocolos de comunicación y formatos de datos. Algunos de esos servicios y sistemas también tendrán varios años de vida, de entre ellos habrá alguno que ya puede ser considerado como obsoleto por la tecnología que utiliza, que ya no recibe soporte de nuevas características y solo es modificado en caso tareas de mantenimiento o en caso de un problema grave de seguridad. Estos servicios heredados aún con su antigüedad siguen siendo importantes por el servicio que prestan.

Sustituir esos servicios o sistemas heredados por otros nuevos a veces no es lo más adecuado ya que intervienen otros factores como el coste de tiempo requerido para desarrollar los nuevos sistemas que reemplacen a los antiguos, el coste económico, la disponibilidad de trabajadores que lo hagan y también por fiabilidad, cambiar un sistema con sus defectos y limitaciones pero que funciona por uno nuevo que no estará exento de sus propios problemas y defectos es un riesgo para el servicio prestado.

En vez de sustituir servicios y sistemas por unos nuevos una opción que se suele utilizar es proporcionar una integración. Apache Camel es una herramienta específica para realizar tareas de integración, que también se puede utilizar aún cuando no sea para un servicio o sistema heredado.

La librería Apache Camel

Apache Camel es una librería ligera destinada a realizar tareas de integración entre servicios y sistemas. La de utilizar esta librería sobre realizar una integración con código propio específico para cada integración es que Apache Camel ya proporciona una buena cantidad de funcionalidades sin necesidad de tener que implementarlas.

A diferencia de las herramientas Enterprise Service Bus o ESB que también sin utilizadas para realizar tareas de integración entre sistemas heterogéneos y que suelen ser herramientas grandes y pesadas, Apache Camel es simplemente una librería muy ligera que es posible utilizarla embebida dentro de otras aplicaciones, por ejemplo dentro de una aplicación de Spring Boot.

Apache Camel soporta multitud de protocolos de comunicación como HTTP, FTP o JMS, formatos de datos como JSON, XML o CSV e  integración con servicios como AWS, Consul o Twitter entre muchos otros. También ya tiene implementados multitud de patrones de integración como choice, filter, muticast, circuit breaker o bulkhead. Otra de sus funcionalidades es que soporta realizar pruebas unitarias.

Conceptos de Apache Camel

Apache Camel utiliza varios conceptos. La integración o funcionalidades desarrolladas se modelan como un flujo, ruta o route que comienza a partir de un origen o consumer y se envía a un destino o producer. En este flujo se tratan mensajes o Exchange que contiene además de los datos del mensaje o payload metadatos como cabeceras asociadas. En los diferentes pasos del flujo el Exchange puede sufrir transformaciones con los procesadores o processor y en el que se aplican los diferentes patrones de integración o integration patterns.

Una parte importante de Apache Camel que lo hacen fácil de utilizar son los endpoints que son URLs compuestas de un esquema, contexto y opciones. Un ejemplo de endpoint es el siguiente del componente RabbitMQ rabbitmq:exchange para tomar como fuente o destino colas de mensajes o del componente File file:misc/ para el sistema de archivos.

Los flujos se modelan con un lenguaje de dominio específico o DSL ya sea definiéndolo con código Java o en un archivo con formato XML. Al utilizar código Java se gana el soporte del entorno integrado de desarrollo, asistencia de código y detección de errores de compilación.

Al igual que en una aplicación de Spring existe el ApplicationContext, Apache Camel posee un contexto a modo de registro con todos los objetos de la instancia de Camel.

La colección de componentes de Apache Camel es muy numerosa.

Los formatos de datos que soporta también son muy numerosos.

También soporta los patrones de integración identificados en el libro Enterprise Integration Patterns que ya han demostrado su utilidad para solventar y simplificar los problemas a los que están dirigidos.

Patrones de integración

Algunos de los patrones básicos que soporta Apache Camel son choice para elegir rutas alternativas a las que dirigir los mensajes, filter para descartar los mensajes que no cumplan alguna condición, multicast para enviar un mensaje a varios destinos, recipient list para enviar a varios destinos de forma dinámica o wire tap para inspeccionar los mensajes sin alterar su flujo normal. Esos son solo unos pocos patrones de integración soportados.

Patrón content based router patrón filter

Patrón multicast Patrón recipient list

Diferentes patrones de integración

Ejemplo básico con Apache Camel

Apache Camel al ser una librería es muy fácil de integrarlo en cualquier tipo de aplicación, en este ejemplo se utiliza Spring Boot. El ejemplo consiste en dos rutas, una que simplemente muestra en la salida los mensajes que se envía, la otra ruta lee los archivos CSV de un directorio que contienen listas de productos en diferentes columnas, filtra los productos que no tienen un importe superior a una cantidad, los transforma y les añade el IVA y finalmente los muestra en la salida, cada vez que en el directorio se añade un CSV se procesa.

Esta es la definición de varias rutas con su DSL en código Java que se definen en las clases que implementan la interfaz RouteBuilder, utilizando Spring definiéndose como un componente son añadidos de forma automática al contexto de Apache Camel. En la ruta HelloWorldRoute simplemente tomo como fuente lo que llega al endpoint de nombre direct:helloworld y lo dirige a la salida del sistema con stream:out sin ningún procesamiento adicional entre el origen y el destino.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package io.github.picodotdev.blogbitix.holamundoapachecamel;

import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;

@Component
public class HelloWorldRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        from("direct:helloworld").routeId("helloworld").to("stream:out");
    }
}
HelloWorldRoute.java

Al inicio del programa se envía al consumidor de la ruta helloworld diez UUID.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package io.github.picodotdev.blogbitix.holamundoapachecamel;

import java.util.UUID;
import java.util.stream.IntStream;

import org.apache.camel.ProducerTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main implements ApplicationRunner {

    @Autowired
    private ProducerTemplate producerTemplate;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        IntStream.range(0, 10).forEach(i -> {
            producerTemplate.sendBody("direct:helloworld", UUID.randomUUID());
        });
    }

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

La siguiente ruta es algo más compleja y muestra varias de las capacidades de Apache Camel. Monitoriza un directorio con un archivo en formato CSV, cuando este se crea o está presente al iniciar la aplicación la ruta lo toma como fuente de datos e inicia su procesamiento en el flujo.

Primeramente se procesan los datos transformándolos en objetos Java de tipo Book que son simplemente objetos POJO con una propiedad por cada columna del CSV. Al procesar los datos se obtiene una lista de objetos de tipo Book, con la operación split, la lista de divide en objetos individuales en el flujo.

Posteriormente, se aplica una condición sobre los objetos, según si el objeto cumple la condición o no se envían a un destino u otro. Según el destino al que están dirigidos se establece un con una cabecera que se transmiten como metadato al mismo tiempo que los datos.

Finalmente, los mensajes llegan al destino direct:books-stream-out, se aplica un filtro sobre la cabecera anterior, si la cumple se aplica un procesamiento al mensaje para aplicar el IVA sobre el precio del libro y una transformación que cambia el tipo del mensaje de Book a una cadena String, para terminar la cadena se envía a stream:out para imprimirlo en la salida de la aplicación.

Una vez procesado el CSV con éxito Apache Camel lo mueve a una carpeta oculta .camel, si el mismo archivo es vuelto a copiar en la capeta se procesa de nuevo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package io.github.picodotdev.blogbitix.holamundoapachecamel;

import java.math.BigDecimal;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.PredicateBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.dataformat.BindyType;
import org.springframework.stereotype.Component;

@Component
public class BooksRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        from("file:misc/").routeId("books-file")
                .unmarshal().bindy(BindyType.Csv, Book.class)
                .split(body())
                .choice()
                .when(simple("${body.price} < 30")).to("direct:books-cheap")
                .otherwise().to("direct:books-expensive");

        from("direct:books-cheap").routeId("books-cheap").setHeader("type", constant("cheap")).to("direct:books-stream-out");
        from("direct:books-expensive").routeId("books-expensive").setHeader("type", constant("expensive")).to("direct:books-stream-out");

        from("direct:books-stream-out").routeId("books-stream-out")
                .filter(header("type").isEqualTo("cheap"))
                .process(new VatProcessor())
                .transform(simple("${body.title} at only ${body.price} €"))
                .to("stream:out");
    }

    private class VatProcessor implements Processor {
        @Override
        public void process(Exchange exchange) throws Exception {
            Book book = (Book) exchange.getMessage().getBody();
            BigDecimal priceWithVat = book.getPrice().multiply(new BigDecimal("1.04"));
            book.setPrice(priceWithVat);
        }
    }
}
BooksRoute.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package io.github.picodotdev.blogbitix.holamundoapachecamel;

import java.math.BigDecimal;

import org.apache.camel.dataformat.bindy.annotation.CsvRecord;
import org.apache.camel.dataformat.bindy.annotation.DataField;

@CsvRecord(separator = ",", crlf = "UNIX")
public class Book {
    @DataField(pos = 1)
    private String title;

    @DataField(pos = 2)
    private String url;

    @DataField(pos = 3)
    private BigDecimal price;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}
Book.java
1
2
3
4
The DevOps Handbook,https://www.amazon.es/DevOPS-Handbook-World-Class-Reliability-Organizations/dp/1942788002/,23
The Phoenix Project,https://www.amazon.es/Phoenix-Project-Devops-Helping-Business/dp/1942788290/,25
The Unicorn Project,https://www.amazon.es/Unicorn-Project-Developers-Disruption-Thriving/dp/1942788762/,24
Site Reliability Engineering,https://www.amazon.es/Site-Reliability-Engineering-Betsy-Beyer/dp/149192912X/,43
books.csv

Esta es la salida del programa.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.3)

2021-08-05 20:38:55.887  INFO 13543 --- [           main] i.g.p.b.holamundoapachecamel.Main        : Starting Main using Java 11.0.10 on archlinux with PID 13543 (/home/picodotdev/Documentos/Software/personal/blog-ejemplos/HolaMundoApacheCamel/app/build/classes/java/main started by picodotdev in /home/picodotdev/Documentos/Software/personal/blog-ejemplos/HolaMundoApacheCamel/app)
2021-08-05 20:38:55.889  INFO 13543 --- [           main] i.g.p.b.holamundoapachecamel.Main        : No active profile set, falling back to default profiles: default
2021-08-05 20:38:57.146  INFO 13543 --- [           main] c.s.b.CamelSpringBootApplicationListener : Starting CamelMainRunController to ensure the main thread keeps running
2021-08-05 20:38:57.155  INFO 13543 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Routes startup summary (total:5 started:5)
2021-08-05 20:38:57.156  INFO 13543 --- [           main] o.a.c.impl.engine.AbstractCamelContext   :     Started books-file (file://misc/)
2021-08-05 20:38:57.156  INFO 13543 --- [           main] o.a.c.impl.engine.AbstractCamelContext   :     Started books-cheap (direct://books-cheap)
2021-08-05 20:38:57.156  INFO 13543 --- [           main] o.a.c.impl.engine.AbstractCamelContext   :     Started books-expensive (direct://books-expensive)
2021-08-05 20:38:57.156  INFO 13543 --- [           main] o.a.c.impl.engine.AbstractCamelContext   :     Started books-stream-out (direct://books-stream-out)
2021-08-05 20:38:57.156  INFO 13543 --- [           main] o.a.c.impl.engine.AbstractCamelContext   :     Started helloworld (direct://helloworld)
2021-08-05 20:38:57.156  INFO 13543 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 3.11.0 (camel-1) started in 234ms (build:31ms init:190ms start:13ms)
2021-08-05 20:38:57.161  INFO 13543 --- [           main] i.g.p.b.holamundoapachecamel.Main        : Started Main in 1.629 seconds (JVM running for 1.958)
26723c08-c1dc-4459-9afa-4a746cad97c8
31e93fa7-9df8-45fa-9852-36deb677b505
f629d26d-396a-4db9-a88e-6f072cde7abe
1c2593e8-09e3-4dcd-becb-9fd6053f5bde
79087761-6d61-4dfa-8a9b-acd6ad83389f
a77b1048-3ea1-46cb-b427-1d5b92d556f6
7e45f1e9-5725-4514-b3cf-cfbf3a406c9a
17e0999a-4d0f-4724-ab14-dd655b7bb991
e999a300-d1c2-4911-8d8f-1f0ad575e4e6
f5b3fb7c-1e22-405b-991b-978bd2e8134f
The DevOps Handbook at only 23.92 €
The Phoenix Project at only 26.00 €
The Unicorn Project at only 24.96 €
System.out

Para su ejecución se utiliza la herramienta de construcción Gradle con el siguiente archivo donde se definen las dependencias del proyecto. La librería de Apache Camel para Spring Boot proporciona la funcionalidad de la que la aplicación se mantenga en funcionamiento tal como ocurre cuando se utiliza la dependencia de Spring para desarrollar aplicaciones web.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
plugins {
    id 'application'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation platform('org.springframework.boot:spring-boot-dependencies:2.5.3')
    implementation platform('org.apache.camel.springboot:camel-spring-boot-bom:3.11.0')

    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.apache.camel.springboot:camel-spring-boot-starter'
    implementation 'org.apache.camel:camel-stream:3.11.0'
    implementation 'org.apache.camel:camel-bindy:3.11.0'
    implementation 'org.apache.camel:camel-csv:3.11.0'
}

application {
    mainClass = 'io.github.picodotdev.blogbitix.holamundoapachecamel.Main'
}
build.gradle
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run


Comparte el artículo: