Modelli di integrazione con Apache Camel

1. Panoramica

Questo articolo tratterà alcuni modelli di integrazione aziendale essenziali (EIP) supportati da Apache Camel. I modelli di integrazione aiutano fornendo soluzioni per metodi standardizzati di integrazione dei sistemi.

Se devi prima esaminare le basi di Apache Camel, visita sicuramente questo articolo per rispolverare le basi.

2. Informazioni sugli EIP

I modelli di integrazione aziendale sono modelli di progettazione che mirano a fornire soluzioni per le sfide di integrazione. Camel fornisce implementazioni per molti di questi modelli. Per vedere l'elenco completo dei modelli supportati, visitare questo collegamento.

In questo articolo, tratteremo i modelli di integrazione di Content Based Router, Message Translator, Multicast, Splitter e Dead Letter Channel.

2. Router basato sui contenuti

Content Based Router è un router di messaggi che instrada un messaggio alla sua destinazione in base a un'intestazione del messaggio, parte del payload o praticamente qualsiasi cosa dallo scambio di messaggi che consideriamo contenuto.

Inizia con l' istruzione DSL choice () seguita da una o più istruzioni DSL when () . Ogni when () contiene un'espressione di predicato che, se soddisfatta, risulterà nell'esecuzione di fasi di elaborazione contenute.

Illustriamo questo EIP definendo un percorso che consuma i file da una cartella e li sposta in due cartelle diverse a seconda dell'estensione del file. Il nostro percorso è referenziato nel file Spring XML utilizzando la sintassi XML personalizzata per Camel:

La definizione del percorso è contenuta nella classe ContentBasedFileRouter in cui i file vengono instradati dalla cartella di origine a due diverse cartelle di destinazione a seconda della loro estensione.

In alternativa, potremmo utilizzare l'approccio di configurazione Spring Java qui invece di utilizzare il file XML Spring. Per fare ciò, dobbiamo aggiungere una dipendenza aggiuntiva al nostro progetto:

 org.apache.camel camel-spring-javaconfig 2.18.1 

L'ultima versione del manufatto può essere trovata qui.

Dopodiché, dobbiamo estendere la classe CamelConfiguration e sovrascrivere il metodo route () che farà riferimento a ContentBasedFileRouter :

@Configuration public class ContentBasedFileRouterConfig extends CamelConfiguration { @Bean ContentBasedFileRouter getContentBasedFileRouter() { return new ContentBasedFileRouter(); } @Override public List routes() { return Arrays.asList(getContentBasedFileRouter()); } }

L'estensione viene valutata utilizzando Simple Expression Language tramite l' istruzione DSL simple () che doveva essere utilizzata per la valutazione di espressioni e predicati:

public class ContentBasedFileRouter extends RouteBuilder { private static final String SOURCE_FOLDER = "src/test/source-folder"; private static final String DESTINATION_FOLDER_TXT = "src/test/destination-folder-txt"; private static final String DESTINATION_FOLDER_OTHER = "src/test/destination-folder-other"; @Override public void configure() throws Exception { from("file://" + SOURCE_FOLDER + "?delete=true").choice() .when(simple("${file:ext} == 'txt'")) .to("file://" + DESTINATION_FOLDER_TXT).otherwise() .to("file://" + DESTINATION_FOLDER_OTHER); } }

Qui stiamo inoltre usando l' istruzione DSL altrimenti () per instradare tutti i messaggi che non soddisfano i predicati forniti con le istruzioni when () .

3. Traduttore di messaggi

Poiché ogni sistema utilizza il proprio formato dati, è spesso necessario tradurre il messaggio proveniente da un altro sistema nel formato dati supportato dal sistema di destinazione.

Camel supporta il router MessageTranslator che ci consente di trasformare i messaggi utilizzando un processore personalizzato nella logica di routing, utilizzando un bean specifico per eseguire la trasformazione o utilizzando l' istruzione DSL transform () .

Un esempio con l'utilizzo di un processore personalizzato può essere trovato nell'articolo precedente in cui abbiamo definito un processore che antepone un timestamp al nome del file di ogni file in arrivo.

Dimostriamo ora come utilizzare Message Translator usando l' istruzione transform () :

public class MessageTranslatorFileRouter extends RouteBuilder { private static final String SOURCE_FOLDER = "src/test/source-folder"; private static final String DESTINATION_FOLDER = "src/test/destination-folder"; @Override public void configure() throws Exception { from("file://" + SOURCE_FOLDER + "?delete=true") .transform(body().append(header(Exchange.FILE_NAME))) .to("file://" + DESTINATION_FOLDER); } }

In questo esempio, aggiungiamo il nome del file al contenuto del file tramite l' istruzione transform () per ogni file dalla cartella di origine e spostiamo i file trasformati in una cartella di destinazione.

4. Multicast

Il multicast ci consente di instradare lo stesso messaggio a una serie di endpoint diversi e di elaborarli in modo diverso .

Ciò è possibile utilizzando l' istruzione DSL multicast () e quindi elencando gli endpoint e le fasi di elaborazione al loro interno.

Per impostazione predefinita, l'elaborazione su endpoint diversi non viene eseguita in parallelo, ma può essere modificata utilizzando l' istruzione DSL parallelProcessing () .

Camel utilizzerà l'ultima risposta come messaggio in uscita dopo i multicast per impostazione predefinita. Tuttavia, è possibile definire una diversa strategia di aggregazione da utilizzare per assemblare le risposte dai multicast.

Vediamo come appare Multicast EIP in un esempio. Metteremo in multicast i file dalla cartella di origine su due percorsi diversi in cui trasformeremo il loro contenuto e li invieremo a diverse cartelle di destinazione. Qui usiamo direct: component che ci permette di collegare due percorsi insieme:

public class MulticastFileRouter extends RouteBuilder { private static final String SOURCE_FOLDER = "src/test/source-folder"; private static final String DESTINATION_FOLDER_WORLD = "src/test/destination-folder-world"; private static final String DESTINATION_FOLDER_HELLO = "src/test/destination-folder-hello"; @Override public void configure() throws Exception { from("file://" + SOURCE_FOLDER + "?delete=true") .multicast() .to("direct:append", "direct:prepend").end(); from("direct:append") .transform(body().append("World")) .to("file://" + DESTINATION_FOLDER_WORLD); from("direct:prepend") .transform(body().prepend("Hello")) .to("file://" + DESTINATION_FOLDER_HELLO); } }

5. Splitter

Lo splitter ci consente di suddividere il messaggio in arrivo in più parti e di elaborarle singolarmente. Ciò è possibile utilizzando l' istruzione DSL split () .

Al contrario di Multicast, Splitter cambierà il messaggio in arrivo, mentre Multicast lo lascerà così com'è.

Per dimostrarlo in un esempio, definiremo un percorso in cui ogni riga di un file viene divisa e trasformata in un singolo file che viene quindi spostato in una cartella di destinazione diversa. Ogni nuovo file verrà creato con il nome del file uguale al contenuto del file:

public class SplitterFileRouter extends RouteBuilder { private static final String SOURCE_FOLDER = "src/test/source-folder"; private static final String DESTINATION_FOLDER = "src/test/destination-folder"; @Override public void configure() throws Exception { from("file://" + SOURCE_FOLDER + "?delete=true") .split(body().convertToString().tokenize("\n")) .setHeader(Exchange.FILE_NAME, body()) .to("file://" + DESTINATION_FOLDER); } }

6. Dead Letter Channel

È comune e ci si dovrebbe aspettare che a volte possono verificarsi problemi, ad esempio deadlock del database, che possono causare il mancato recapito di un messaggio come previsto. Tuttavia, in alcuni casi, riprovare con un certo ritardo aiuterà e un messaggio verrà elaborato.

Dead Letter Channel allows us to control what happens with a message once it fails to be delivered. Using Dead Letter Channel we can specify whether to propagate the thrown Exception to the caller and where to route the failed Exchange.

When a message fails to be delivered, Dead Letter Channel (if used) will move the message to the dead letter endpoint.

Let's demonstrate this on an example by throwing an exception on the route:

public class DeadLetterChannelFileRouter extends RouteBuilder { private static final String SOURCE_FOLDER = "src/test/source-folder"; @Override public void configure() throws Exception { errorHandler(deadLetterChannel("log:dead?level=ERROR") .maximumRedeliveries(3).redeliveryDelay(1000) .retryAttemptedLogLevel(LoggingLevel.ERROR)); from("file://" + SOURCE_FOLDER + "?delete=true") .process(exchange -> { throw new IllegalArgumentException("Exception thrown!"); }); } }

Here we defined an errorHandler which logs failed deliveries and defines redelivery strategy. By setting retryAttemptedLogLevel(), each redelivery attempt will be logged with specified log level.

In order for this to be fully functional, we additionally need to configure a logger.

Dopo aver eseguito questo test, in una console sono visibili le seguenti istruzioni di log:

ERROR DeadLetterChannel:156 - Failed delivery for (MessageId: ID-ZAG0025-50922-1481340325657-0-1 on ExchangeId: ID-ZAG0025-50922-1481340325657-0-2). On delivery attempt: 0 caught: java.lang.IllegalArgumentException: Exception thrown! ERROR DeadLetterChannel:156 - Failed delivery for (MessageId: ID-ZAG0025-50922-1481340325657-0-1 on ExchangeId: ID-ZAG0025-50922-1481340325657-0-2). On delivery attempt: 1 caught: java.lang.IllegalArgumentException: Exception thrown! ERROR DeadLetterChannel:156 - Failed delivery for (MessageId: ID-ZAG0025-50922-1481340325657-0-1 on ExchangeId: ID-ZAG0025-50922-1481340325657-0-2). On delivery attempt: 2 caught: java.lang.IllegalArgumentException: Exception thrown! ERROR DeadLetterChannel:156 - Failed delivery for (MessageId: ID-ZAG0025-50922-1481340325657-0-1 on ExchangeId: ID-ZAG0025-50922-1481340325657-0-2). On delivery attempt: 3 caught: java.lang.IllegalArgumentException: Exception thrown! ERROR dead:156 - Exchange[ExchangePattern: InOnly, BodyType: org.apache.camel.component.file.GenericFile, Body: [Body is file based: GenericFile[File.txt]]]

Come puoi notare, ogni tentativo di riconsegna viene registrato mostrando Exchange per il quale la consegna non è andata a buon fine.

7. Conclusione

In questo articolo, abbiamo presentato un'introduzione ai modelli di integrazione utilizzando Apache Camel e li abbiamo dimostrati su alcuni esempi.

Abbiamo dimostrato come utilizzare questi modelli di integrazione e perché sono utili per risolvere le sfide di integrazione.

Il codice di questo articolo può essere trovato su GitHub.