Eines der wichtigsten Enterprise Integration Patterns in der API-Integration ist das Scatter-and-Gather-Muster. Es beinhaltet das Sammeln mehrerer Antworten von Backends und deren Konsolidierung in eine einzige Ausgabe, die oft über das Frontend zugänglich ist.
In diesem Artikel werden wir ein Szenario untersuchen, bei dem wir Angebote für den Stundensatz für das Streichen von Häusern von APIs verschiedener Anbieter sammeln, konsolidieren und in einer einheitlichen Ansicht im Frontend präsentieren. Wir werden dies mit Apache Camel auf Basis von Spring Boot tun.
Für ein besseres Verständnis des Scatter-and-Gather-Musters kannst du das Dokument hier nachlesen.
Initiale Projekteinrichtung
Initiale Projekteinrichtung a. Um das initiale Projekt zu generieren, können wir den Spring Boot Initializer verwenden. Lass uns die untenstehende Auswahl für die Projektgenerierung verwenden. Unter den Abhängigkeiten kannst du sehen, dass ich Apache Camel ausgewählt habe.

Sobald wir die Apache Camel-Abhängigkeit ausgewählt haben, wird die untenstehende Abhängigkeit in der pom.xml enthalten sein.
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>4.4.1</version>
</dependency>
Für den Build in diesem Artikel verwenden wir Apache Maven 3.8.6 und Java Version: 18.0.2 auf einem Apple M1 Pro, mit Ziel auf Java Version 11.
Für weitere Einblicke in die Projektstruktur kannst du den vorherigen Artikel zu Apache Camel Teil-1 und Teil-2 nachlesen.
Diagramm des Integrationsflusses auf hoher Ebene
Wie im obigen Diagramm dargestellt, haben wir eine Frontend-API namens GET /quotes/painting. Diese API wird gleichzeitig die drei Anbieter-APIs aufrufen, die Antworten von jedem abrufen, sie zusammenführen und dann eine konsolidierte Antwort an den Client senden.
Implementierung
Verständnis der pom.xml
Nr. | Abhängigkeit | Beschreibung |
1 | camel-spring-boot-starter | Hilft bei der Integration von Camel-Routen innerhalb der Spring Boot-Anwendungen |
2 | camel-rest-starter | Unterstützt Camel-Routen zur Konfiguration als RESTful Services |
3 | lombok | Erleichtert die Entwicklung, z.B. durch slf4j-Logging. Bietet Getter und Setter durch einfache Verwendung von Anmerkungen |
4 | camel-netty-http | Camel unterstützt REST-Komponenten wie Jetty, Servlet, Undertow und hier verwende ich Netty |
5 | Properties -> java.version | Definiert 11, sodass die Kompatibilität mit JDK 11 bestätigt wird und die Klassen-Dateien für die spezifische Hauptversion generiert werden |
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/>
</parent>
<groupId>com.quotes</groupId>
<artifactId>PaintingServiceQuotes</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>PaintingServiceQuotes</name>
<description>Project for Painting Service Costs</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-bom</artifactId>
<version>4.4.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-rest-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-netty-http</artifactId>
<version>4.4.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Implementierung der Spring Boot Hauptanwendungsklasse
package com.quotes.painting;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PaintingServiceQuotesApplication {
public static void main(String[] args) {
SpringApplication.run(PaintingServiceQuotesApplication.class, args);
}
}
Backend API Route Builder
Wir implementieren den Backend API Route Builder, um die anbieter-spezifischen APIs zu haben. Wie du sehen kannst, haben wir 3 Anbieter-APIs definiert, und sie werden application/json-Antworten zurücksenden.
package com.quotes.painting.builders.api.back;
import lombok.extern.slf4j.Slf4j;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class BackApiBuilder extends RouteBuilder {
@Value("${custom.netty.http.port}")
private int nettyHttpPort;
@Override
public void configure() throws Exception {
log.info("BackApiBuilder Route Builder Initiated.");
restConfiguration()
.component("netty-http")
.port(nettyHttpPort);
// 1. API of the Vendor One
rest("/quotes")
.get("/vendor-one")
.produces("application/json")
.to("direct:vendorOne");
from("direct:vendorOne")
.log("Received GET request for vendorOne.")
.setBody(constant("{\"vendor\": \"vendorOne\", \"serviceType\": \"Painting\", \"Hourly\": \"25$\"}"));
// 2. API of the Vendor Two
rest("/quotes")
.get("/vendor-two")
.produces("application/json")
.to("direct:vendorTwo");
from("direct:vendorTwo")
.log("Received GET request for vendorTwo.")
.setBody(constant("{\"vendor\": \"vendorTwo\", \"serviceType\": \"Painting\", \"Hourly\": \"35$\"}"));
// 3. API of the Vendor Three
rest("/quotes")
.get("/vendor-three")
.produces("application/json")
.to("direct:vendorThree");
from("direct:vendorThree")
.log("Received GET request for vendorThree.")
.setBody(constant("{\"vendor\": \"vendorThree\", \"serviceType\": \"Painting\", \"Hourly\": \"15$\"}"));
}
}
Front API Route Builder
Die folgende Klasse enthält die Hauptimplementierung der Scatter-and-Gather-Musterlogik. In diesem Beispiel habe ich die Multicast-Camel-Komponente verwendet. Standardmäßig arbeitet Multicast auf einem einzelnen Thread, aber parallele Verarbeitung kann ebenfalls aktiviert werden. Außerdem wurde die „AggregationStrategy“ verwendet, um die Antwort-Payloads zu kombinieren und als konsolidierte JSON-Antwort zurückzusenden.
package com.quotes.painting.builders.api.front;
import com.quotes.painting.aggregaters.JsonAggregator;
import lombok.extern.slf4j.Slf4j;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class FrontApiBuilder extends RouteBuilder {
@Value("${custom.netty.http.port}")
private int nettyHttpPort;
@Override
public void configure() throws Exception {
log.info("FrontApiBuilder Route Builder Initiated.");
restConfiguration()
.component("netty-http")
.port(nettyHttpPort);
rest("/quotes")
.get("/painting")
.produces("application/json")
.to("direct:paintingQuotes");
from("direct:paintingQuotes")
.log("PaintingQuotes Request Initiated.")
.multicast()
.parallelProcessing()
.aggregationStrategy(new JsonAggregator())
.to("netty-http:http://localhost:8090/quotes/vendor-one")
.to("netty-http:http://localhost:8090/quotes/vendor-two")
.to("netty-http:http://localhost:8090/quotes/vendor-three")
.end()
.log("Process Completed: ${body}");
}
}
Implementierung der Aggregation Strategy
Diese Klasse hilft, die Antworten der Backend-APIs zu aggregieren.
package com.quotes.painting.aggregaters;
import org.apache.camel.AggregationStrategy;
import org.apache.camel.Exchange;
import org.springframework.stereotype.Component;
@Component
public class JsonAggregator implements AggregationStrategy {
@Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
if (oldExchange == null) {
return newExchange;
}
String oldBody = oldExchange.getIn().getBody(String.class);
String newBody = newExchange.getIn().getBody(String.class);
String body = null;
if (!oldBody.startsWith("[")) {
body = "[ " + oldBody + ", " + newBody + " ]";
} else{
body = oldBody.replace("]", "") + ", " + newBody + " ]";
}
oldExchange.getIn().setBody(body);
return oldExchange;
}
}
Resources/application.yml-Konfiguration
camel:
springboot:
name: PaintingServiceQuotes
logging:
level:
org:
apache:
camel: INFO
custom:
netty:
http:
port: 8090
Build und Test
Führe das typische Maven-Build-Kommando im Hauptverzeichnis des Projekts aus:
mvn clean install
Führe dann das folgende Kommando aus, um die Spring Boot-Anwendung zu starten:
java -jar target/PaintingServiceQuotes-0.0.1-SNAPSHOT.jar
Verwende einen Client oder Curl-Befehl, um die Frontend-API aufzurufen, die mit der aggregierten Antwort antworten wird.
Damit endet die Diskussion darüber, wie wir das Scatter-and-Gather Enterprise Integration Pattern mit Apache Camel implementieren können. Hoffentlich hat diese Schritt-für-Schritt-Anleitung eine detaillierte Erklärung gegeben, wie wir dies mit Camel erreichen können.
Ich hoffe, dich in einem weiteren Blog wiederzusehen. Bleib dran für weitere Einblicke und Updates.