Перейти к основному содержимому

Шаблоны интеграции с Apache Camel

· 7 мин. чтения

1. Обзор

В этой статье будут рассмотрены некоторые важные шаблоны корпоративной интеграции (EIP), поддерживаемые Apache Camel. Шаблоны интеграции помогают, предоставляя решения для стандартизированных способов интеграции систем.

Если вам нужно сначала ознакомиться с основами Apache Camel, обязательно посетите эту статью , чтобы освежить в памяти основы.

2. О EIP

Шаблоны корпоративной интеграции — это шаблоны проектирования, направленные на решение проблем интеграции. Camel предоставляет реализации для многих из этих шаблонов. Чтобы увидеть полный список поддерживаемых шаблонов, перейдите по этой ссылке .

В этой статье мы рассмотрим шаблоны интеграции Маршрутизатор на основе содержимого, Переводчик сообщений, Многоадресную рассылку, Разделитель и Канал недоставленных сообщений.

2. Маршрутизатор на основе контента

Контентно-ориентированный маршрутизатор — это маршрутизатор сообщений, который направляет сообщение к месту назначения на основе заголовка сообщения, части полезной нагрузки или, в основном, чего-либо из обмена сообщениями, который мы рассматриваем как контент.

Он начинается с оператора DSL selection () , за которым следует один или несколько операторов DSL when() . Каждое выражение when() содержит предикатное выражение, выполнение которого приведет к выполнению содержащихся в нем шагов обработки.

Давайте проиллюстрируем этот EIP, определив маршрут, который использует файлы из одной папки и перемещает их в две разные папки в зависимости от расширения файла. На наш маршрут ссылаются в XML-файле Spring с использованием пользовательского синтаксиса XML для Camel:

<bean id="contentBasedFileRouter" 
class="com.foreach.camel.file.ContentBasedFileRouter" />

<camelContext xmlns="http://camel.apache.org/schema/spring">
<routeBuilder ref="contentBasedFileRouter" />
</camelContext>

Определение маршрута содержится в классе ContentBasedFileRouter , где файлы направляются из исходной папки в две разные папки назначения в зависимости от их расширения.

В качестве альтернативы мы могли бы использовать здесь подход конфигурации Spring Java, а не использовать XML-файл Spring. Для этого нам нужно добавить в наш проект дополнительную зависимость:

<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-javaconfig</artifactId>
<version>2.18.1</version>
</dependency>

Последнюю версию артефакта можно найти здесь .

После этого нам нужно расширить класс CamelConfiguration и переопределить метод route () , который будет ссылаться на ContentBasedFileRouter :

@Configuration
public class ContentBasedFileRouterConfig extends CamelConfiguration {

@Bean
ContentBasedFileRouter getContentBasedFileRouter() {
return new ContentBasedFileRouter();
}

@Override
public List<RouteBuilder> routes() {
return Arrays.asList(getContentBasedFileRouter());
}
}

Расширение оценивается с использованием простого языка выражений с помощью простого () оператора DSL, который предназначен для использования для оценки выражений и предикатов:

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);
}
}

Здесь мы дополнительно используем оператор DSL else () для маршрутизации всех сообщений, которые не удовлетворяют предикатам, заданным операторами when() .

3. Переводчик сообщений

Поскольку каждая система использует свой собственный формат данных, часто требуется преобразовать сообщение, поступающее из другой системы, в формат данных, поддерживаемый целевой системой.

Camel поддерживает маршрутизатор MessageTranslator , который позволяет нам преобразовывать сообщения, используя либо пользовательский процессор в логике маршрутизации, либо специальный bean-компонент для выполнения преобразования, либо оператор DSL transform() .

Пример использования пользовательского процессора можно найти в предыдущей статье , где мы определили процессор, который добавляет временную метку к имени каждого входящего файла.

Теперь давайте продемонстрируем, как использовать переводчик сообщений с помощью оператора 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);
}
}

В этом примере мы добавляем имя файла к содержимому файла с помощью оператора transform() для каждого файла из исходной папки и перемещаем преобразованные файлы в папку назначения.

4. Мультикаст

Многоадресная рассылка позволяет нам направлять одно и то же сообщение на множество разных конечных точек и обрабатывать их по-разному .

Это возможно с помощью оператора multicast() DSL, а затем путем перечисления конечных точек и шагов обработки внутри них.

По умолчанию обработка на разных конечных точках не выполняется параллельно, но это можно изменить с помощью оператора DSL parallelProcessing() .

Верблюд будет использовать последний ответ в качестве исходящего сообщения после многоадресной рассылки по умолчанию. Однако можно определить другую стратегию агрегирования, которая будет использоваться для сборки ответов от многоадресных рассылок.

Давайте посмотрим, как выглядит Multicast EIP на примере. Мы будем многоадресно передавать файлы из исходной папки по двум разным маршрутам, где мы будем преобразовывать их содержимое и отправлять их в разные папки назначения. Здесь мы используем компонент direct:, который позволяет нам связать два маршрута вместе:

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. Сплиттер

Разделитель позволяет нам разделить входящее сообщение на несколько частей и обрабатывать каждую из них по отдельности. Это возможно с помощью оператора DSL split() .

В отличие от Multicast, Splitter изменит входящее сообщение, а Multicast оставит его как есть.

Чтобы продемонстрировать это на примере, мы определим маршрут, в котором каждая строка из файла разделяется и преобразуется в отдельный файл, который затем перемещается в другую папку назначения. Каждый новый файл будет создан с именем файла, равным содержимому файла:

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. Канал мертвых писем

Это распространено, и следует ожидать, что иногда могут возникать проблемы, например взаимоблокировки базы данных, которые могут привести к тому, что сообщение не будет доставлено должным образом. Однако в некоторых случаях повторная попытка с определенной задержкой может помочь, и сообщение будет обработано.

Канал недоставленных писем позволяет нам контролировать, что происходит с сообщением, когда оно не может быть доставлено. Используя канал недоставленных сообщений, мы можем указать, следует ли распространять выброшенное исключение вызывающей стороне и куда направить неудавшийся обмен.

Когда сообщение не может быть доставлено, канал недоставленных сообщений (если используется) переместит сообщение в конечную точку недоставленных сообщений.

Продемонстрируем это на примере, сгенерировав исключение на маршруте:

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!");
});
}
}

Здесь мы определили обработчик ошибок , который регистрирует неудачные доставки и определяет стратегию повторной доставки. При установке retryAttemptedLogLevel() каждая попытка повторной доставки будет регистрироваться с указанным уровнем журнала.

Для того, чтобы это было полнофункционально, нам дополнительно нужно настроить логгер.

После выполнения этого теста в консоли отображаются следующие операторы журнала:

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]]]

Как вы можете заметить, каждая попытка повторной доставки регистрируется с указанием Exchange, для которого доставка не удалась.

7. Заключение

В этой статье мы представили введение в шаблоны интеграции с использованием Apache Camel и продемонстрировали их на нескольких примерах.

Мы продемонстрировали, как использовать эти шаблоны интеграции и почему они полезны для решения проблем интеграции.

Код из этой статьи можно найти на GitHub .