В последние годы программное обеспечение ESB становится все более популярным. Если большинство людей обычно знают, что такое ESB, им не хватает четкого понимания точной роли различных компонентов такой архитектуры.
Например, Apache ServiceMix состоит из трех основных компонентов: Apache Karaf (контейнер OSGI), Apache ActiveMQ (брокер сообщений) и Apache Camel. Кстати, что именно верблюд? Что такое «механизм routing and mediation engine
»? Для чего это полезно?
Я работаю с Camel уже около года, и я думаю — хотя я вовсе не являюсь гуру Camel, что у меня теперь достаточно задних мыслей, чтобы вы открыли для себя интерес и силу Camel, используя некоторые очень конкретные примеры. Для ясности, в оставшейся части этой статьи я буду использовать Spring DSL — при условии, что читатель знаком с синтаксисом Spring.
Вариант использования
Давайте представим, что мы хотим реализовать следующий сценарий с использованием Camel. Запросы на информацию о продукте поступают в виде простых файлов (в формате CSV) в определенной папке. Каждая строка такого файла содержит один запрос конкретного клиента о конкретной модели автомобиля. Мы хотим отправить этим клиентам электронное письмо об интересующем их автомобиле. Для этого сначала нужно вызвать веб-сервис для получения дополнительных данных о клиенте (например, их адрес электронной почты). Затем мы должны получить характеристики автомобиля (скажем, текст) из базы данных. Поскольку мы хотим, чтобы наши письма выглядели достойно (т. Е. HTML), также потребуется небольшое преобразование текста.
Конечно, мы не хотим простой последовательной обработки запросов, но хотели бы ввести некоторый параллелизм. Точно так же мы не хотим отправлять много раз одну и ту же почту разным клиентам (а одну и ту же уникальную почту нескольким получателям). Было бы также неплохо использовать возможности кластеризации нашего бэк-энда для балансировки нагрузки при обращении к веб-сервисам. И, наконец, в случае сбоя обработки запроса мы хотим, так или иначе, отслеживать исходный запрос, чтобы мы могли, например, отправить его по почте.
A (возможная) реализация Camel:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
<? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation="http://www.springframework.org/schema/beans > <!-- 2 redeliveries max before failed message is placed into a DLQ --> < errorHandler id = "myDLQ" type = "DeadLetterChannel" deadLetterUri = "activemq:queue:errors" useOriginalMessage = "true" > < redeliveryPolicy maximumRedeliveries = "2" /> </ errorHandler > <!-- The polling of a specific folder every 30 sec --> < route id = "route1" > < from uri = "file:///Users/bli/folderToPoll?delay=30000&delete=true" /> < unmarshal > < csv /> </ unmarshal > < split > < simple >${body}</ simple > < setHeader headerName = "customerId" > < simple >${body[1]}</ simple > </ setHeader > < setHeader headerName = "carModelId" > < simple >${body[2]}</ simple > </ setHeader > < setBody > < simple >${body[0]}</ simple > </ setBody > < to uri = "activemq:queue:individualRequests?disableReplyTo=true" /> </ split > </ route > <!-- The consumption of individual (jms) mailing requests --> < route id = "route2" > < from uri = "activemq:queue:individualRequests?maxConcurrentConsumers=5" /> < pipeline > < to uri = "direct:getCustomerEmail" /> < to uri = "direct:sendMail" /> </ pipeline > </ route > <!-- Obtain customer email by parsing the XML response of a REST web service --> < route id = "route3" > < from uri = "direct:getCustomerEmail" /> < setBody > < constant /> </ setBody > < loadBalance > < roundRobin /> < to uri = "http://backend1.mycompany.com/ws/customers?id={customerId}&authMethod=Basic&authUsername=geek&authPassword=secret" /> < to uri = "http://backend2.mycompany.com/ws/customers?id={customerId}&authMethod=Basic&authUsername=geek&authPassword=secret" /> </ loadBalance > < setBody > < xpath resultType = "java.lang.String" >/customer/general/email</ xpath > </ setBody > </ route > <!-- Group individual sendings by car model --> < route id = "route4" > < from uri = "direct:sendMail" /> < aggregate strategyRef = "myAggregator" completionSize = "10" > < correlationExpression > < simple >header.carModelId</ simple > </ correlationExpression > < completionTimeout > < constant >60000</ constant > </ completionTimeout > < setHeader headerName = "recipients" > < simple >${body}</ simple > </ setHeader > < pipeline > < to uri = "direct:prepareMail" /> < to uri = "direct:sendMailToMany" /> </ pipeline > </ aggregate > </ route > <!-- Prepare the mail content --> < route id = "route5" > < from uri = "direct:prepareMail" /> < setBody > < simple >header.carModelId</ simple > </ setBody > < pipeline > < to uri = "sql:SELECT xml_text FROM template WHERE template_id =# ?dataSourceRef=myDS" /> < to uri = "xslt:META-INF/xsl/email-formatter.xsl" /> </ pipeline > </ route > <!-- Send a mail to multiple recipients --> < route id = "route6" > < from uri = "direct:sendMailToMany" /> < to uri = "smtp://mail.mycompany.com:25?username=geek&password=secret&[email protected]&to={recipients}&subject=Your request&contentType=text/html" /> < log message = "Mail ${body} successfully sent to ${headers.recipients}" /> </ route > </ camelContext > <!-- Pure Spring beans referenced in the various Camel routes --> <!-- The ActiveMQ broker --> < bean id = "activemq" class = "org.apache.activemq.camel.component.ActiveMQComponent" > < property name = "brokerURL" value = "tcp://localhost:61616" /> </ bean > <!-- A datasource to our database --> < bean id = "myDS" class = "org.apache.commons.dbcp.BasicDataSource" > < property name = "driverClassName" value = "org.h2.Driver" /> < property name = "url" value = "jdbc:h2:file:/Users/bli/db/MyDatabase;AUTO_SERVER=TRUE;TRACE_LEVEL_FILE=0" /> < property name = "username" value = "sa" /> < property name = "password" value = "sa" /> </ bean > <!-- An aggregator implementation --> < bean id = "myAggregator" class = "com.mycompany.camel.ConcatBody" /> </ beans > |
И код (только!) Java-класса:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
public class ConcatBody implements AggregationStrategy { public static final String SEPARATOR = ", " ; public Exchange aggregate(Exchange aggregate, Exchange newExchange) { if (aggregate == null ) { // The aggregation for the very exchange item is the exchange itself return newExchange; } else { // Otherwise, we augment the body of current aggregate with new incoming exchange String originalBody = aggregate.getIn().getBody(String. class ); String bodyToAdd = newExchange.getIn().getBody(String. class ); aggregate.getIn().setBody(originalBody + SEPARATOR + bodyToAdd); return aggregate; } } } |
Некоторые объяснения
- « Route1 » имеет дело с обработкой входящих плоских файлов. Содержимое файла сначала разбирается (в формате CSV), а затем разбивается на строки / записи. Каждая строка будет превращена в отдельное уведомление, которое отправляется в очередь JMS.
- « Route2 » использует эти уведомления. По сути, выполнение запроса означает выполнение двух последовательных действий («конвейер»): получение электронной почты клиента (route3) и отправка ему почты (route4). Обратите внимание на параметр maxConcurrentConsumers, который используется для простого соответствия нашему требованию параллелизма.
- « Route3 » моделирует, как получить электронную почту клиента: просто анализируя (используя XPath) XML-ответ (защищенной) веб-службы REST, доступной на двух внутренних узлах.
- « Route4 » содержит логику для отправки массивных писем. Каждый раз, когда собираются 10 одинаковых запросов на отправку (то есть в нашем случае 10 запросов на одну и ту же модель автомобиля) (и мы не готовы ждать более 1 минуты), мы хотим, чтобы весь процесс был продолжен новым сообщением (или «Обмен» в терминологии верблюдов) — объединение 10 собранных сообщений. Продолжение процесса означает: сначала подготовьте почтовое тело (маршрут 5), а затем отправьте его группе (маршрут 6).
- В « route5 » выдается запрос SQL, чтобы получить соответствующий текст в зависимости от модели автомобиля. Для этого результата мы применяем небольшое преобразование XSL-T (которое заменит текущее тело обмена на вывод преобразования xsl).
- При вводе « route6 » обмен содержит все, что нам нужно. У нас есть список получателей (в качестве заголовка), и у нас также есть (в теле) HTML-текст для отправки. Поэтому теперь мы можем перейти к реальной отправке по протоколу SMTP.
- В случае ошибок (например, временных проблем с сетью) — в любом месте всего процесса Camel сделает максимум две дополнительные попытки, прежде чем отказаться. В этом последнем случае исходное сообщение будет автоматически помещено Camel в очередь недоставленных писем JMS.
Вывод
Верблюд — действительно отличная структура — не идеальная, но все же отличная. Вы будете удивлены, увидев, сколько строк кода необходимо для моделирования сложного сценария или маршрута. Вы также можете быть рады видеть, насколько прозрачен ваш код, как быстро ваши коллеги могут понять логику ваших маршрутов.
Но это, конечно, не главное преимущество. Использование Camel в первую очередь предлагает вам мыслить в терминах корпоративных интеграционных шаблонов (также называемых «EIP»); он помогает вам разложить исходную сложность на менее сложные (возможно, параллельные) подпути, используя хорошо известные и проверенные методы, что приводит к более модульным, более гибким реализациям. В частности, использование методов развязки облегчает потенциальную замену или рефакторинг отдельных частей или компонентов вашего решения.
Справка: Откройте для себя мощь Apache Camel от нашего партнера по W4G Бернарда Линьи .