Padrões de integração com o Apache Camel
1. Visão geral
Este artigo abordará alguns padrões essenciais de integração corporativa (EIPs) suportados pelo Apache Camel. Os padrões de integração ajudam fornecendo soluções para formas padronizadas de integração de sistemas.
Se você precisa primeiro revisar o básico do Apache Camel, definitivamente visitethis article para se atualizar no básico.
2. Sobre EIPs
Os padrões de integração corporativa são padrões de design que visam fornecer soluções para os desafios de integração. O Camel fornece implementações para muitos desses padrões. Para ver a lista completa de padrões suportados, visitethis link.
Neste artigo, abordaremos os padrões de integração de roteador baseado em conteúdo, tradutor de mensagem, multicast, divisor e canal morto.
2. Roteador Baseado em Conteúdo
O roteador baseado em conteúdo é um roteador de mensagens que direciona uma mensagem para seu destino com base no cabeçalho da mensagem, parte da carga útil ou basicamente qualquer coisa da troca de mensagens que consideramos conteúdo.
Ele começa com a instrução DSLchoice() seguida por uma ou mais instruções DSLwhen(). Cadawhen() contém uma expressão de predicado que, se satisfeita, resultará na execução de etapas de processamento contidas.
Vamos ilustrar esse EIP definindo uma rota que consome arquivos de uma pasta e os move para duas pastas diferentes, dependendo da extensão do arquivo. Nossa rota é referenciada no arquivo XML Spring usando a sintaxe XML personalizada para Camel:
A definição de rota está contida na classeContentBasedFileRouter, onde os arquivos são roteados da pasta de origem para duas pastas de destino diferentes, dependendo de sua extensão.
Como alternativa, poderíamos usar a abordagem de configuração do Spring Java aqui em vez de usar o arquivo XML da Spring. Para fazer isso, precisamos adicionar uma dependência adicional ao nosso projeto:
org.apache.camel
camel-spring-javaconfig
2.18.1
A versão mais recente do artefato pode ser encontradahere.
Depois disso, precisamos estender a classeCamelConfiguration e substituir o métodoroutes() que fará referência aContentBasedFileRouter:
@Configuration
public class ContentBasedFileRouterConfig extends CamelConfiguration {
@Bean
ContentBasedFileRouter getContentBasedFileRouter() {
return new ContentBasedFileRouter();
}
@Override
public List routes() {
return Arrays.asList(getContentBasedFileRouter());
}
}
A extensão é avaliada usandoSimple Expression Language viasimple () instrução DSL que se destinava a ser usada para avaliar expressões e predicados:
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);
}
}
Aqui, estamos usando adicionalmente a instrução DSLotherwise() para rotear todas as mensagens que não satisfazem os predicados dados com as instruçõeswhen().
3. Tradutor de mensagens
Uma vez que cada sistema usa seu próprio formato de dados, é frequentemente necessário traduzir a mensagem vinda de outro sistema para o formato de dados suportado pelo sistema de destino.
O Camel oferece suporte ao roteadorMessageTranslator, o que nos permite transformar mensagens usando um processador personalizado na lógica de roteamento, usando um bean específico para realizar a transformação ou usando a instrução DSLtransform().
Exemplo com o uso de um processador personalizado pode ser encontrado emprevious article, onde definimos um processador que adiciona um carimbo de data / hora para cada nome de arquivo de entrada.
Vamos agora demonstrar como usar o Message Translator usando a instruçãotransform():
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);
}
}
Neste exemplo, estamos anexando o nome do arquivo ao conteúdo do arquivo por meio da instruçãotransform() para cada arquivo da pasta de origem e movendo os arquivos transformados para uma pasta de destino.
4. Multicast
O multicast nos permiteroute the same message to a set of different endpoints and process them in a different way.
Isso é possível usando a instrução DSLmulticast() e listando os terminais e as etapas de processamento dentro deles.
Por padrão, o processamento em terminais diferentes não é feito em paralelo, mas isso pode ser alterado usando a instrução DSLparallelProcessing().
O Camel usará a última resposta como mensagem de saída após as multicasts por padrão. No entanto, é possível definir uma estratégia de agregação diferente a ser usada para montar as respostas dos multicasts.
Vamos ver como o Multicast EIP se parece em um exemplo. Faremos multicast dos arquivos da pasta de origem para duas rotas diferentes, onde transformaremos seu conteúdo e os enviaremos para pastas de destino diferentes. Aqui usamosdirect: component, que nos permite conectar duas rotas:
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. Splitter
O divisor nos permitesplit the incoming message into a number of pieces and processing each of them individually. Isso é possível usando a instrução DSLsplit().
Ao contrário do Multicast, o Splitter alterará a mensagem recebida, enquanto o Multicast deixará como está.
Para demonstrar isso em um exemplo, vamos definir uma rota onde cada linha de um arquivo é dividida e transformada em um arquivo individual que é então movido para uma pasta de destino diferente. Cada novo arquivo será criado com o nome do arquivo igual ao conteúdo do arquivo:
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
É comum e deve-se esperar que, às vezes, ocorram problemas, por exemplo, conflitos de banco de dados, o que pode fazer com que uma mensagem não seja entregue conforme o esperado. No entanto, em certos casos, tentar novamente com um certo atraso ajudará e uma mensagem será processada.
Dead Letter Channel allows us to control what happens with a message once it fails to be delivered. Usando Dead Letter Channel, podemos especificar se devemos propagar a exceção lançada para o chamador e para onde rotear a troca com falha.
Quando uma mensagem não é entregue, o canal de mensagens não entregues (se usado) move a mensagem para o terminal de mensagens não entregues.
Vamos demonstrar isso em um exemplo lançando uma exceção na rota:
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!");
});
}
}
Aqui, definimos umerrorHandler que registra as entregas com falha e define a estratégia de entrega. DefinindoretryAttemptedLogLevel(), cada tentativa de reenvio será registrada com o nível de registro especificado.
Para que isso seja totalmente funcional, precisamos adicionalmente configurar um logger.
Após a execução deste teste, as seguintes instruções de log são visíveis em um console:
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]]]
Como você pode notar, cada tentativa de reenvio está sendo registrada, exibindo o Exchange para o qual a entrega não foi bem-sucedida.
7. Conclusão
Neste artigo, apresentamos uma introdução aos padrões de integração usando o Apache Camel e os demonstramos em alguns exemplos.
Demonstramos como usar esses padrões de integração e por que eles são benéficos para resolver desafios de integração.
O código deste artigo pode ser encontradoover on GitHub.