Разработка обработчиков Camel

Обработчик — класс с публичным конструктором без аргументов и одним или несколькими методами, отмеченными @From. Класс необходимо отдельно зарегистрировать в camel.handler.<n>.class.

Минимальный обработчик

class OrderHandler {
    @From(endpoint = "amq:queue:orders")
    fun process(message: String) {
        OrderService().process(message)
    }
}

Метод должен иметь ровно один аргумент. Методы без аргументов и методы с двумя или более аргументами пропускаются при построении маршрутов. Метод должен быть доступен для рефлексивного вызова; на практике следует объявлять его публичным.

@From: входной endpoint

@From — единственная обязательная аннотация метода. Endpoint можно записать готовой строкой:

@From(endpoint = "amq:queue:orders")
fun process(message: String) {
    OrderService().process(message)
}

Поле endpoint сохранено для совместимости и помечено в исходном коде как устаревшее. Для нового обработчика предпочтительна составная запись:

@From(
    type = ComponentType.ACTIVEMQ,
    path = [
        From.PathParameter("queue"),
        From.PathParameter("orders")
    ]
)
fun process(message: String) {
    OrderService().process(message)
}
type

Тип компонента. Сейчас доступен только ComponentType.ACTIVEMQ, который создаёт схему amq.

path

Части пути, соединяемые двоеточием. Пример выше создаёт amq:queue:orders.

query

Параметры после ?, соединяемые через &.

Если одновременно заполнены endpoint и составные поля, используется endpoint.

Контекстные переменные endpoint

Входной endpoint обрабатывается шаблонизатором ERP. Плагин передаёт переменную server:instanceId, что позволяет создать отдельную durable-подписку для каждого экземпляра:

@From(
    type = ComponentType.ACTIVEMQ,
    path = [
        From.PathParameter("topic"),
        From.PathParameter("notifications")
    ],
    query = [
        From.QueryParameter("clientId", "{{server:instanceId}}"),
        From.QueryParameter("durableSubscriptionName", "{{server:instanceId}}")
    ]
)
fun process(message: String) {
    NotificationService().process(message)
}

В результате получится endpoint вида:

amq:topic:notifications?clientId=erp-1&durableSubscriptionName=erp-1

Шаблонизация выполняется только для @From. Значение @To.endpoint передаётся Camel без обработки контекстных переменных ERP.

Аргумент метода

Перед вызовом тело сообщения преобразуется в тип единственного аргумента.

Тип аргумента Поведение

String

Строковое тело передаётся без JSON-десериализации.

Типизированный объект

Строка или массив байтов десериализуется выбранным transformer.

Параметризованный тип

Используется полный reflection-тип аргумента, поэтому поддерживаются, например, списки DTO.

Exchange

В метод передаётся исходный Camel Exchange без преобразования.

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

Обработчик запускается внутри серверного контекста служебного пользователя ERP. Если для работы требуется соединение с БД, сервис или DAO должен получить его принятым в проекте способом; Camel сам транзакцию не открывает.

@To: выходной endpoint

@To отправляет возвращённое значение метода в указанный endpoint:

@From(endpoint = "amq:queue:uppercase")
@To(endpoint = "amq:topic:lowercase")
fun process(message: String): String = message.lowercase()

Метод с @To обязан возвращать значение. Если возвращаемый тип — void/Unit, метод пропускается при регистрации маршрутов. Без @To возвращённое значение игнорируется.

Перед отправкой результат сериализуется тем же transformer, который использовался для чтения. Результат null устанавливает пустое тело сообщения.

@Transform: преобразование тела

Без аннотации используется GsonTransformer. Он сериализует и десериализует JSON через общий экземпляр Gson.

Для Kotlin DTO и моделей с настройками Jackson следует указать:

@Transform(transformer = JacksonTransformer::class)

JacksonTransformer:

  • игнорирует неизвестные свойства;

  • поддерживает Kotlin-классы;

  • подставляет пустые коллекции и map для null;

  • сериализует даты как числовые метки времени;

  • использует часовой пояс JVM.

Можно реализовать собственный наследник Transformer. У его класса должен быть публичный конструктор без аргументов.

@Throttle: ограничение скорости

@Throttle(messages = 100, period = 1, timeUnit = TimeUnit.SECONDS)
messages

Максимальное количество сообщений за период.

period

Размер периода. По умолчанию 1000.

timeUnit

Единица периода. По умолчанию миллисекунды.

Аннотация преобразуется в стандартный Camel throttler и применяется перед вызовом метода.

@Redelivery: повторная доставка и DLQ

@Redelivery(
    attempts = 3,
    deadLetterEndpoint = "amq:queue:orders.DLQ",
    delayMillis = 1000,
    backoffMultiplier = 2.0
)
Параметр По умолчанию Назначение

attempts

Обязательный

Число дополнительных доставок после первой неудачной обработки.

deadLetterEndpoint

Обязательный

Endpoint, куда Camel отправит сообщение после исчерпания повторов.

delayMillis

0

Задержка между повторами в миллисекундах.

backoffMultiplier

0.0

Коэффициент увеличения задержки.

delayPattern

Пустая строка

Паттерн задержек Camel, например 1:2000;3:5000.

Перед отправкой в DLQ к сообщению добавляется заголовок error вида:

TransformationException: Deserialization failed: class java.lang.String -> class example.Order

Исключение, выброшенное методом, разворачивается из InvocationTargetException для текста заголовка и журнала.

Текущая реализация задаёт backoffMultiplier, но явно не включает режим exponential backoff. Если рост задержки критичен для сценария, его следует проверить интеграционным тестом на используемой версии Camel.

Порядок применения аннотаций

Маршрут строится в фиксированном порядке:

From → Redelivery → Throttle → Transform и вызов метода → To

Порядок аннотаций в исходном коде на это не влияет.

@Parameter

Аннотация @Parameter присутствует в пакете плагина, но текущий parser и route builder её не читают. Она не влияет на аргумент метода или endpoint и не должна использоваться в новых обработчиках.

Регистрация и проверка

После добавления класса:

  1. добавьте camel.handler.<n>.class в конфигурацию Camel;

  2. сохраните конфигурацию либо перекомпилируйте зарегистрированный динамический класс;

  3. проверьте строку Добавлен обработчик: <полное имя класса> в журнале;

  4. отправьте валидное сообщение и проверьте побочный эффект;

  5. отправьте ошибочное сообщение и проверьте повторную доставку и DLQ, если они настроены;

  6. проверьте метрики camel_processing_count_total и camel_processing_duration_milliseconds.

Аннотационные unit-тесты покрывают построение входного endpoint и подстановку server:instanceId. Для нового прикладного обработчика необходимо отдельно тестировать разбор DTO, бизнес-логику и ошибочный сценарий.