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

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

1. обзор

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

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

2. О EIP

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

В этой статье мы рассмотрим шаблоны интеграции маршрутизатора на основе контента, транслятора сообщений, многоадресной рассылки, разделителя и канала мертвых сообщений.

2. Контент-маршрутизатор

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

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

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




    

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

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


    org.apache.camel
    camel-spring-javaconfig
    2.18.1

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

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

@Configuration
public class ContentBasedFileRouterConfig extends CamelConfiguration {

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

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

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

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

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

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

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

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

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

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

Multicast позволяет намroute the same message to a set of different endpoints and process them in a different way.

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

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

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

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

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. Разветвитель

Разделитель позволяет намsplit the incoming message into a number of pieces and processing each of them individually.. Это возможно с помощью оператора DSLsplit().

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

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

Dead Letter Channel allows us to control what happens with a message once it fails to be delivered. Используя канал мертвых писем, мы можем указать, следует ли передавать выброшенное исключение вызывающей стороне и куда направлять отказавший Exchange.

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

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

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

Здесь мы определилиerrorHandler, который регистрирует неудавшиеся доставки и определяет стратегию повторной доставки. При установке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 и продемонстрировали их на нескольких примерах.

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

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