Een van de belangrijkste Enterprise Integration Patterns in API-integratie is het Scatter and Gather-patroon. Dit patroon houdt in dat meerdere reacties van backends worden verzameld en samengevoegd tot één enkele uitvoer, die vaak via de frontend toegankelijk is.
In dit artikel verkennen we een scenario waarin we offertes verzamelen voor het uurtarief voor het verven van huizen van API’s van verschillende aanbieders, deze samenvoegen en in een uniforme weergave aan de frontend presenteren. We gaan dit doen met Apache Camel bovenop Spring Boot.
Voor een beter begrip van het Scatter and Gather-patroon kun je het document hier raadplegen.
Initiële projectopzet
Om het initiële project te genereren, kunnen we de Spring Boot Initializer gebruiken. Laten we de onderstaande selectie gebruiken voor projectgeneratie. Onder afhankelijkheden kun je zien dat ik Apache Camel heb geselecteerd.
Zodra we de Apache Camel-afhankelijkheid selecteren, wordt de onderstaande afhankelijkheid in pom.xml opgenomen.
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>4.4.1</version>
</dependency>
Voor de build in dit artikel zullen we Apache Maven 3.8.6 en Java versie: 18.0.2 op een Apple M1 Pro gebruiken, met als doel Java versie 11.
Voor meer inzichten in de projectstructuur kun je het vorige artikel over Apache Camel Deel-1 en Deel-2 raadplegen.
Diagram van het integratieproces op hoog niveau
Zoals geïllustreerd in het bovenstaande diagram, hebben we een front-end API genaamd GET /quotes/painting. Deze API zal gelijktijdig de drie leverancier-API’s aanroepen, de reactie van elk ophalen, samenvoegen en vervolgens een geconsolideerde reactie naar de client sturen.
Implementatie
Begrip van de pom.xml
Nr. | Afhankelijkheid | Beschrijving |
1 | camel-spring-boot-starter | Helpt bij de integratie van Camel-routes binnen de Spring Boot-toepassingen |
2 | camel-rest-starter | Ondersteunt Camel-routes om als RESTful Services te configureren |
3 | lombok | Voor het gemak van ontwikkeling zoals slf4j-logging. Biedt getters en setters door simpelweg annotaties te gebruiken |
4 | camel-netty-http | Camel ondersteunt restcomponenten zoals Jetty, Servlet, Undertow en hier gebruik ik Netty |
5 | Properties -> java.version | Definieert 11, zodat de compatibiliteit met JDK 11 wordt bevestigd en de klassebestanden worden gegenereerd voor de specifieke hoofdversie. |
<?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>
Implementatie van de Spring Boot hoofdtoepassingsklasse
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);
}
}
Backends API Route Builder
We implementeren de Backend API Route builder om de leverancier specifieke API’s te hebben. Zoals je kunt zien, hebben we 3 leverancier-API’s gedefinieerd, en zij zullen application/json-reacties terugsturen.
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
De volgende klasse bevat de hoofdimlementatie van de scatter and gather patroonlogica. In dit voorbeeld heb ik de multicast-camelcomponent gebruikt. Standaard werkt multicast op een enkele thread, maar parallelle verwerking kan ook worden ingeschakeld. Ook is “AggregationStrategy” gebruikt om de antwoordpayloads te combineren en terug te sturen als geconsolideerde JSON-reactie.
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}");
}
}
Implementatie van Aggregation Strategy
Deze klasse helpt bij het samenvoegen van de reacties van de backend API’s.
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-configuratie
camel:
springboot:
name: PaintingServiceQuotes
logging:
level:
org:
apache:
camel: INFO
custom:
netty:
http:
port: 8090
Bouw en Test
Voer het typische maven build-commando uit in de hoofdmap van het project:
mvn clean install
Voer vervolgens het onderstaande commando uit om de Spring Boot-toepassing te starten:
java -jar target/PaintingServiceQuotes-0.0.1-SNAPSHOT.jar
Gebruik een client of curl-commando om de front-end API aan te roepen, die zal reageren met de geaggregeerde reactie.
Dat is het einde van de discussie over hoe we het scatter and gather Enterprise Integration Pattern kunnen implementeren met Apache Camel. Hopelijk heeft deze stapsgewijze handleiding een gedetailleerde uitleg gegeven over hoe we dit met Camel kunnen bereiken.
Tot de volgende blog. Blijf op de hoogte voor meer inzichten en updates.