Введение

В данной статье я бы хотел раскрыть тему создания сложных маршрутов в Apache Camel с помощью компонента Direct.

Пример задачи

Предположим что перед нами стоит задача создания маршрутов со следующими характеристиками:

  1. Входная точка - REST.

  2. Поддержка throttling или rate limiting политик на входе.

  3. Возможность ветвления запроса в маршруте на основе некоторого условия (header, body и т.д.).

  4. Поддержка throttling или rate limiting политик для каждой из веток маршрута отдельно.

Решение

Одним из возможных путей решения поставленной задачи является разбиение сложного маршрута на несколько простых. Для реализации такого разбиения отлично подходит стандартный компонент Apache Camel - Direct.

Из документации Apache Camel: “This endpoint can be used to connect existing routes in the same camel context.”

Реализация

Примечание: данная статья проверенно актуальна для Apache Camel версий 3.12-3.19

Создадим класс ComplexRouteBuilder, который является наследником абстрактного класса RouteBuilder и объявим реализацию метода configure().

Примечание: в данном примере метод configure() уже содержит вызовы пошагового построения составного маршрута, а также в первой строчке объявление коллекции для задания значений ветвления.

private static final List<String> CHOICE_ROUTES_NAMES = Arrays.asList("choice_1", "choice_2", "choice_3");

@Override
public void configure() throws Exception {
    defineInitialStep();
    defineThrottlingStep();
    defineChoiceStep();
    defineDestinationStep();
}

Точка входа в маршрут

В качестве точки входа объявим маршрут, слушающий REST запросы по адресу {contextPath}/api/complex_route и передающий сообщение дальше в direct:throttling.

private void defineInitialStep() {
    var firstPartRouteDefinition = rest("/api")
            .get("/complex_route")
            .to("direct:throttling");
}

Ограничение пропускной способности всего маршрута

Для ограничения пропускной способности маршрута объявим промежуточный шаг, на который применим Throttling Policy. Точка входа промежуточного маршрута - direct:throttling, выход - direct:choice.

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

    private void defineThrottlingStep() {
        var throttlingPartRouteDefinition = from("direct:throttling")
                .to("direct:choice");
        applyThrottlingPolicy(throttlingPartRouteDefinition);
    }
    
    private void applyThrottlingPolicy(RouteDefinition routeDefinition) {
        routeDefinition
                .throttle(10)
                .timePeriodMillis(1000)
                .rejectExecution(true)
                .onException(ThrottlerRejectedExecutionException.class)
                    .to("bean:logger")
                    .handled(true)
                    .process("routeErrorProcessor")
                    .stop()
                .end();
    }

Ветвление маршрута

Маршрут ветвления слушает сообщения из direct:choice и, в данном случае на основании контента в одном из заголовков запроса, перенаправляет их в соответствующий маршрут direct:{choice_route_name}_destination.

private void defineChoiceStep() {
    var choicePartRouteDefinition = from("direct:choice");
    var choiceDefinition = choicePartRouteDefinition.choice();
    CHOICE_ROUTES_NAMES.forEach(o -> {
        Predicate choicePredicate = header(o).isEqualTo(o + " header value");
        choicePartRouteDefinition.choice().when(choicePredicate)
                .to("direct:" + o + "_destination");
    });
    choiceDefinition.endChoice();
}

Точка назначения

Для каждого из вариантов ветвления создадим соответствующий маршрут, ведущий в конечный сервис. Соответственно для этой группы маршрутов точкой входа будет direct:{choice_route_name}_destination, а точкой выхода - http адрес на конечном сервисе.

Также для маршрута из этой группы применим ThrottlingPolicy для соотвествия начальному условию ограничения пропускной способности на каждой ветви (код метода applyThrottlingPolicy приводился выше).

private void defineDestinationStep() {
    CHOICE_ROUTES_NAMES.forEach(o -> {
        var destinationRouteDefinition = from("direct:" + o + "_destination")
                .to("http://localhost:9191/api/v1/test/first?bridgeEndpoint=true&someParam=" + o);
        applyThrottlingPolicy(destinationRouteDefinition);
    });
}

Схема маршрута из примера

Итог

По итогу приведённых действий мы построили довольно комплексный маршрут с большим количеством правил.

Данный подход применим как в сторону упрощения политик и правил так и в сторону их усложения.

Плюсы

  1. Слабая связность объявлений промежуточных маршрутов. Позволяет гибко структурировать код.

  2. Улучшение соответствия кода принципу Single Responsibility. Что может улучшить контроль за его качеством.

  3. Меньшая зависимость от реализации стандартных классов для объявления последовательностей операций.

Минусы

  1. Слабая связность требует большего контроля за соблюдением архитектуры.

  2. Производительность и потребление памяти. На небольших примерах это не так заметно. Но в целом создание промежуточных звеньев-маршрутов требует дополнительной памяти и вычислительной мощности. В каком именно размере и насколько сильно влияет такой подход на производтельность - на данный момент я не знаю.

Спасибо за внимание.

Комментарии (3)


  1. postromantic83
    01.11.2022 12:27
    +1

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

    Вообще тема верблюдоводства развивается, раз появляются новые статьи


  1. Akvel
    01.11.2022 12:57
    +1

    Ага, норм решение, еще из плюсов появляется возможность переиспользования кода. Пару моментов по коду.

    1. На choce лучше сразу вешать otherwise с выдачей красивой ошибки

    2. Посмотрите Endpoint dsl https://camel.apache.org/manual/Endpoint-dsl.html cильно упрощает жизнь. И процессоры можно явно объектами объявлять в пути.

    А вы где используете apache camel если не секрет?


    1. MasteR_GeliOS Автор
      01.11.2022 16:13
      +1

      1. Да, согласен насчёт дополнительного otherwise в choice для ошибки.

      2. Endpoint DSL выглядит очень интересно, спасибо.

      Насчёт использования - подробно не могу раскрыть ответ, просто на одном коммерческом проекте столкнулся с использованием Apache Camel. И пока просто интересно его изучать.