Spring boot велик и могуч, но есть есть и другие инструменты.
Camel позволяет абстрагироваться от кода и сосредоточиться на конфигурации, взаимодействии компонент. Camel это интеграционный клей, который соединяет компоненты системы, предоставляя коннекторы. Примеры коннекторов: rest, ftp, Spring bean и еще много всяких: https://camel.apache.org/components/next/index.html#_components.
Небольшой пример.
Задача - разослать обновления цен из офиса в подразделения (торговые точки). И офис и подразделения имеют свои сервера с развернутыми Spring Boot Applications. Ниже примеры xml Spring context и Camel конфигураций из проекта https://github.com/cherepakhin/el59. Проект старый, предприятия давно уже нет, поэтому опубликовал на github. Почему Spring context задан в xml, а не использованы аннотации? Удобнее было менять логику работы комплекса БЕЗ перекомпиляции.) В примере настроено несколько конфигураций Camel (тег route) для рассылки обновлений в торговые точки из центра.
Camel context загружается вместе со Spring context. Подключение конфигурации Camel (camel.xml) в Spring context.
springContext.xml:
<beans> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <import resource="dao.xml" /> <!--Spring DAO --> <import resource="services.xml" /> <!--Spring services --> <import resource="camel.xml"/> <!--CAMEL!!! -->
В dao.xml и services.xml определены Spring beans. Пример определения Spring bean:
<beans> <!-- Configure Spring beans --> <bean id="senderBestTags" class="ru.perm.v.office.service.SenderBestTags"> <property name="shopProvider" ref="shopDao"/> <property name="bestTagToPriceConvertor" ref="bestTagToPriceConvertor"/> <property name="emailer" ref="emailer"/> </bean> ... </beans>
(BestTag - это пользовательский класс, описание товара, его цена и т.п. Значения задаются в центре и должны быть синхронизированы между центром и подразделениями.)
Класс SenderBestTags:
/** * Отправка изменений ценников в магазин */ public class SenderBestTags { private IEmailer emailer; // какая-то реализация сервиса почты,
// н.п. https://docs.spring.io/spring-framework/reference/integration/email.html public Object process(@Body Object body) { emailer.send(emailReceivers, getMailMessage(), subject, attachFiles); } public IEmailer getEmailer() { return emailer; } public void setEmailer(IEmailer emailer) { this.emailer = emailer; } }
Класс конструирует сообщение и отправляет его по почте. Чуть ниже подробнее про использование этого класса. Сейчас главное, что класс умеет отправлять почту.
MessageEntity - контейнер сообщения для Camel (или не Camel, в том смысле, что это простой класс никак несвязанный ни со Spring, ни с Camel ):
public class MessageEntity<T> implements Serializable { /* * Сообщение (команда) для удаленной системы * Пример: создать (typeCommand = Create) * строку в накладной (className = DocDetail) * для магазина (shopCod='07443') * с содержимым из entity */ protected TypeCommand typeCommand; // тип сущности (н.п. DocDetail) protected String className; // в какой торговой точке (маршрут) protected String shopCod; // содержимое сообщения (см. ниже "Пример сообщения из очереди" // <entity type="DocDetail">...</entity> ) protected T entity; public MessageEntity() { this.typeCommand = TypeCommand.CREATE; this.className = ""; this.shopCod = ""; } public TypeCommand getTypeCommand() { return this.typeCommand; } public void setTypeCommand(TypeCommand typeCommand) { this.typeCommand = typeCommand; } public T getEntity() { return this.entity; } public void setEntity(T entity) { this.entity = entity; } ... }
camel.xml:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd"> <import resource="camel_route.xml"/> <!--CAMEL ROUTE!!! См.ниже --> <!--Spring beans for Camel--> <bean id="ConvertorXmlBestTags" class="ru.perm.v.office.camelcontext.receiver.ConvertorXmlBestTags"> <property name="type" value="ru.perm.v.office.dto.BestTags" /> <property name="entity" value="ru.perm.v.office.dto.BestTags" /> <property name="protocolForTag" ref="protocolForTag"/> </bean> ... </beans>
camel_route.xml (ключевые теги для понимания Camel: route, from, when, to):
<?xml version="1.0" encoding="UTF-8"?> <!-- Configures the Camel route --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd"> ... <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring"> <propertyPlaceholder id="properties" location="classpath:other.properties"/> <!-- route!--> <route> <from uri="jms:queue:to_shop" /> <when> <xpath>//shopCod = '07260'</xpath> <to uri="{{ftp}}/mqto07260?username={{ftp-user}}&password={{ftp-password}}" /> </when> <when> <xpath>//shopCod = '07443'</xpath> <to uri="{{ftp}}/mqto07443?username={{ftp-user}}&password={{ftp-password}}" /> </when> ... <when> <xpath>shopCod ='*'</xpath> <multicast> <to uri="{{ftp}}/mqto07260?username={{ftp-user}}&password={{ftp-password}}&reconnectDelay=60000" /> <to uri="{{ftp}}/mqto07863?username={{ftp-user}}&password={{ftp-password}}&reconnectDelay=1000" /> <to uri="{{ftp}}/mq?username={{ftp-user}}&password={{ftp-password}}&reconnectDelay=60000" /> <to uri="file:c:/temp/?fileName=debug_monitor.xml" /> </multicast> </when> <otherwise/> </route> <route> <from uri="jms:queue:updateq" /> <bean ref="ConvertorFromOfficeDB" method="getXML" /> <to uri="jms:queue:to_shop" /> </route> <route> <from uri="quartz2://myGroup/changenn?cron=0+57+09+*+*+?" /> <bean ref="creatorChangeNN" method="process" /> <to uri="{{ftp}}/tmp/data/?fileName=CHANGENN.XML&username={{ftp-user}}&password={{ftp-password}}&binary=true" /> </route> ... </camelContext>
from uri="jms:queue:to_shop": из очереди "to_shop" ActiveMQ (это сервис работы с очередями сообщений , наподобие Kafka) принимается xml файл-задание на отправку сообщения. Получение сообщения из ActiveMQ служит сигналом для запуска обработки (маршрута). Пример сообщения из очереди:
<?xml version="1.0" encoding="UTF-8"?> <message> <command>CREATE</command> <className>DocDetail</className> <shopCod>07260</shopCod> <!-- shop cod!!! need for camel route --> <entity type="DocDetail"> <n>23166</n> <docn>14941</docn> <nnum>71069178</nnum> <qty>1.0</qty> <price>10000.0</price> </entity> </message>
В зависимости от тега shopCod используя XPath ("<xpath>//shopCod = '07260'</xpath>" в route.xml) файл отправляется по тому или иному маршруту Camel. Конфигурация camel.xml:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd"> <import resource="camel_route.xml"/> <bean id="ConvertorXmlBestTags" class="ru.perm.v.office.camelcontext.receiver.ConvertorXmlBestTags"> <property name="type" value="ru.perm.v.office.dto.BestTags" /> <property name="entity" value="ru.perm.v.office.dto.BestTags" /> <property name="protocolForTag" ref="protocolForTag"/> ...
использованы следующие компоненты Camel:
- from - источник сообщения
- jms:queue:updateq - получение сообщения из очереди ActiveMQ ("updateq" - топик)
- quartz2 - планировщик
- bean - отправка сообщения в Spring bean (подробнее об этом ниже)
- multicast - отправка сообщения по нескольким маршрутам
{{ftp}}, {{ftp-user}}, {{ftp-password}} - значения из jdbc.properties в camel_route.xml (см. выше).
ftp=ftp://127.0.0.1 ftp-user=night ftp-password=n123
События для старта обработки ("from"):
- 'from quarz' - запуск по таймеру
- 'from uri="{{ftp}}...' - запуск сканирования ftp папки
- 'from uri="jms:queue:to_shop' - появилось сообщение в очереди ActiveMQ "to_shop"
Вызов метода process из Spring bean senderBestTags по таймеру (конфигурация. См. начало статьи "Camel позволяет абстрагироваться от кода..."):
<from uri="quartz2://myGroup/changenn?cron=0+57+09+*+*+?" /> <bean ref="senderBestTags" method="process" />
Java код Spring bean класса SenderBestTags (компонета. См. начало статьи "Camel позволяет абстрагироваться от кода..."):
import org.apache.camel.Body; // body camel-message import ru.perm.v.office.dto.message.MessageBestTags; ... public class SenderBestTags { ... private IEmailer emailer; ... public Object process(@Body Object body) { // body camel-message // MessageBestTags - частный dto класс проекта. См.ниже. MessageBestTags messageBestTags = (MessageBestTags) body; ... emailer.send(getEmail(), getMailMessage(),getMailSubject(), getAttaches()); ... } }
MessageBestTags:
package ru.perm.v.office.dto.message; import ru.perm.v.office.dto.BestTags; public class MessageBestTags extends MessageEntity { protected BestTags entity; public MessageBestTags() { this.className = BestTags.class.getSimpleName(); } public BestTags getEntity() { return this.entity; } public void setEntity(BestTags entity) { this.entity = entity; } }
Можно описывать маршруты в коде (статья на Habr Apache Camel и Spring Boot)
@Component class TimedJobs extends RouteBuilder { @Override public void configure() { from("timer:new-discount?delay=1000&period={{discount.newDiscountPeriod:2000}}") .routeId("make-discount") .bean("discountService", "makeDiscount") .to("jpa:org.apache.camel.example.spring.boot.rest.jpa.Discount") .log("Created %${body.amount} discount for ${body.product.name}"); // additional route... }
В начале статьи я объяснил почему я использовал xml, а не код. (Удобнее менять логику работы коплекса БЕЗ перекомпиляции.)
Maven зависимости Camel в pom.xml проекта:
<properties> <spring.group>org.springframework</spring.group> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>3.0.5.RELEASE</spring.version> <camel.version>2.13.1</camel.version> </properties> ... <dependencies> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> <version>${camel.version}</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-quartz2</artifactId> <version>${camel.version}</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-ftp</artifactId> <version>${camel.version}</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-jms</artifactId> <version>${camel.version}</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-mail</artifactId> <version>${camel.version}</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-xstream</artifactId> <version>${camel.version}</version> </dependency> ... </dependencies>
Если используется конфигурирование с application.yaml и аннотации, то для запуска подсистемы Camel нужно добавить в application.yaml:
camel: springboot: main-run-controller: true
Полный пример в https://github.com/cherepakhin/camel_rest/blob/dev/src/main/resources/application.yaml
Говоря о Camel нужно упомянуть о Spring Integration. Простой пример: (TODO: дать ссылку на мебельщиков), Sample-integration, Spring Integration — динамические потоки данных (Habr). Если в "чистом" Camel используются простые Spring beans (аннотация @Bean) и потом они подаются в Camel для использования, то Spring Integration как-бы "подминает" под себя Camel. "Spring Integration (не только, но в основном) посвящена обмену сообщениями . Spring Integration — это, по сути, встроенная корпоративная сервисная шина , которая позволяет вам легко подключать вашу бизнес-логику к каналам обмена сообщениями" (Spring Integration – разработка приложения с нуля (часть 1 / 2)).
Что-то типа отладки.
Есть код SimpleRouteBuilder.java:
import org.apache.camel.builder.RouteBuilder; import org.apache.camel.model.rest.RestBindingMode; import org.springframework.stereotype.Component; @Component public class SimpleRouteBuilder extends RouteBuilder { @Override public void configure() throws Exception { // Camel REST service here. Using the Camel servlet component restConfiguration() .bindingMode(RestBindingMode.auto); rest("/say") .description("Simple Route REST service") .consumes("application/json") .produces("application/json") .outType(Todo.class) .responseMessage().code(200).message("All todos successfully returned").endResponseMessage() .to("bean:todoService?method=getSimple"); } }
И можно этот код запустить и отдебажить. Для этого нужно перейти в каталог java-файла с Camel компонентой и выполнить:
@v:~/prog/java/camel/camel_try_1/src/main/java/ru/perm/v/camel_try_1$ camel run SimpleRouteBuilder.java --dev INFO 146219 --- [ main] org.apache.camel.main.MainSupport : Apache Camel (JBang) 4.8.0 is starting INFO 146219 --- [ main] org.apache.camel.main.MainSupport : Using Java 17.0.13 with PID 146219. Started by vasi in /home/vasi/prog/java/camel/camel_try_1/src/main/java/ru/perm/v/camel_try_1 INFO 146219 --- [ main] org.apache.camel.main.ProfileConfigurer : The application is starting with profile: dev INFO 146219 --- [ main] he.camel.cli.connector.LocalCliConnector : Camel JBang CLI enabled INFO 146219 --- [ main] e.camel.impl.engine.AbstractCamelContext : Apache Camel 4.8.0 (SimpleRouteBuilder) is starting INFO 146219 --- [ main] vertx.core.spi.resolver.ResolverProvider : Using the default address resolver as the dns resolver could not be loaded INFO 146219 --- [ntloop-thread-0] tform.http.vertx.VertxPlatformHttpServer : Vert.x HttpServer started on 0.0.0.0:8080 INFO 146219 --- [ main] upport.FileWatcherResourceReloadStrategy : Live route reloading enabled (directory: .) INFO 146219 --- [ main] e.camel.impl.engine.AbstractCamelContext : Routes startup (total:3 rest-dsl:3) INFO 146219 --- [ main] e.camel.impl.engine.AbstractCamelContext : Started route1 (rest://get:/say:/hello) INFO 146219 --- [ main] e.camel.impl.engine.AbstractCamelContext : Started route2 (rest://get:/say:/bye) INFO 146219 --- [ main] e.camel.impl.engine.AbstractCamelContext : Started route3 (rest://post:/say:/bye) INFO 146219 --- [ main] e.camel.impl.engine.AbstractCamelContext : Apache Camel 4.8.0 (SimpleRouteBuilder) started in 974ms (build:0ms init:0ms start:974ms) INFO 146219 --- [ main] ponent.platform.http.main.MainHttpServer : HTTP endpoints summary INFO 146219 --- [ main] ponent.platform.http.main.MainHttpServer : http://0.0.0.0:8080/say/bye (GET) (accept:application/json) INFO 146219 --- [ main] ponent.platform.http.main.MainHttpServer : http://0.0.0.0:8080/say/hello (GET)
HTTP запрос (httpie):
@v:~$ http http://0.0.0.0:8080/say/hello HTTP/1.1 200 OK Content-Type: application/json transfer-encoding: chunked "Hello World"
Ссылки:
- Простой проект с Java, Spring Boot, Camel https://github.com/cherepakhin/camel_boot_rest
- https://camel.apache.org/
- https://camel.apache.org/
- Apache Camel Development Guide
- Camel + Spring (https://github.com/cherepakhin/camel_spring)
- Учебное пособие по Apache Camel
- Apache Camel и Spring Boot(Habr)
- Использование Apache Camel с Kotlin и Spring Boot (https://github.com/cherepakhin/camel_rest)
- Camel в application.yaml (https://github.com/cherepakhin/camel_rest/blob/dev/src/main/resources/application.yaml)
- Интеграционные RestAssured тесты для проекта camel_rest
- Apache Camel with Spring Boot
- Apache Camel: How to Expose Rest API
- Apache Camel REST DSL
- Spring Boot Example with Camel REST DSL and Platform HTTP
- Welcome to the Apache Camel Spring-Boot Examples
- Writing Your First Camel Spring Boot Project With the Rest DSL
- Getting Started with Camel Spring Boot
- Get started with REST services with Apache Camel
- Таблица соответствия версий Camel и Spring Boot:
$ camel version list --runtime=spring-boot --minimum-version=3.0 CAMEL VERSION SPRING-BOOT JDK KIND RELEASED SUPPORTED UNTIL 3.1.0 2.2.4.RELEASE 8,11 February 2020 3.2.0 2.2.6.RELEASE 8,11 April 2020 3.3.0 2.2.7.RELEASE 8,11 May 2020 3.4.0 2.3.0.RELEASE 8,11 LTS June 2020 June 2021 3.4.1 2.3.1.RELEASE 8,11 LTS July 2020 June 2021 3.4.2 2.3.1.RELEASE 8,11 LTS July 2020 June 2021 3.4.3 2.3.2.RELEASE 8,11 LTS August 2020 June 2021 3.4.4 2.3.4.RELEASE 8,11 LTS September 2020 June 2021 3.4.5 2.3.4.RELEASE 8,11 LTS December 2020 June 2021 3.4.6 2.3.10.RELEASE 8,11 LTS June 2021 June 2021 3.5.0 2.3.3.RELEASE 8,11 September 2020 3.6.0 2.3.4.RELEASE 8,11 September 2020 3.7.0 2.4.0 8,11 LTS December 2020 January 2022 3.7.1 2.4.2 8,11 LTS January 2021 January 2022 3.7.2 2.4.2 8,11 LTS February 2021 January 2022 3.7.3 2.4.3 8,11 LTS March 2021 January 2022 3.7.4 2.4.5 8,11 LTS May 2021 January 2022 3.7.5 2.4.8 8,11 LTS July 2021 January 2022 3.7.6 2.4.11 8,11 LTS October 2021 January 2022 3.7.7 2.4.11 8,11 LTS December 2021 January 2022 3.8.0 2.4.2 8,11 February 2021 3.9.0 2.4.4 8,11 March 2021 3.10.0 2.4.5 8,11 May 2021 3.11.0 2.5.1 8,11 LTS June 2021 July 2022 3.11.1 2.5.3 8,11 LTS August 2021 July 2022 3.11.2 2.5.4 8,11 LTS September 2021 July 2022 3.11.3 2.5.5 8,11 LTS October 2021 July 2022 3.11.4 2.5.6 8,11 LTS November 2021 July 2022 3.11.5 2.5.8 8,11 LTS December 2021 July 2022 3.11.6 2.5.10 8,11 LTS March 2022 July 2022 3.11.7 2.5.13 8,11 LTS May 2022 July 2022 3.12.0 2.5.5 8,11 October 2021 3.13.0 2.5.6 8,11 November 2021 3.14.0 2.6.1 8,11 LTS December 2021 December 2023 3.14.1 2.6.3 8,11 LTS January 2022 December 2023 3.14.2 2.6.4 8,11 LTS March 2022 December 2023 3.14.3 2.6.7 8,11 LTS May 2022 December 2023 3.14.4 2.6.8 8,11 LTS June 2022 December 2023 3.14.5 2.6.10 8,11 LTS August 2022 December 2023 3.14.6 2.6.13 8,11 LTS November 2022 December 2023 3.14.7 2.6.13 8,11 LTS December 2022 December 2023 3.15.0 2.6.3 11 February 2022 3.16.0 2.6.4 11 March 2022 3.17.0 2.6.7 11,17 May 2022 3.18.0 2.7.1 11,17 LTS July 2022 July 2023 3.18.1 2.7.2 11,17 LTS August 2022 July 2023 3.18.2 2.7.3 11,17 LTS September 2022 July 2023 3.18.3 2.7.5 11,17 LTS October 2022 July 2023 3.18.4 2.7.6 11,17 LTS December 2022 July 2023 3.18.5 2.7.8 11,17 LTS January 2023 July 2023 3.18.6 2.7.8 11,17 LTS April 2023 July 2023 3.19.0 2.7.3 11,17 October 2022 3.20.0 2.7.6 11,17 LTS December 2022 December 2023 3.20.1 2.7.7 11,17 LTS January 2023 December 2023 3.20.2 2.7.8 11,17 LTS February 2023 December 2023 3.20.3 2.7.10 11,17 LTS March 2023 December 2023 4.0.0-M1 3.0.2 17 RC February 2023 4.0.0-M2 3.0.4 17 RC March 2023
- Spring Boot – Using Spring Boot with Apache Camel