Usando subfluxos na integração do Spring
1. Visão geral
A integração do Spring facilita o uso de algunsEnterprise Integration Patterns. Uma dessas maneiras é por meio deits DSL.
Neste tutorial, daremos uma olhada no suporte do DSL para subfluxos para simplificar algumas de nossas configurações.
2. Nossa Tarefa
Digamos que temos uma sequência de inteiros que queremos separar em três grupos diferentes.
E se quiséssemos usar o Spring Integration para fazer isso, poderíamos começar criando três canais de saída:
-
Números como 0, 3, 6 e 9 irão para omultipleOfThreeChannel
-
Números como 1, 4, 7 e 10 irão para oremainderIsOneChannel
-
E números como 2, 5, 8 e 11 vão pararemainderIsTwoChannel
Para ver como os subfluxos podem ser úteis, vamos começar com a aparência disso sem subfluxos.
E então, usaremos subfluxos para simplificar nossa configuração com:
-
publicarSubscribeChannel
-
routeToRecipients
-
Filters, para configurar nossa lógicaif-then
-
Routers, para configurar nossa lógicaswitch
3. Pré-requisitos
Agora, antes de configurar nossos subfluxos, vamos criar esses canais de saída.
Faremos essesQueueChannels, pois é um pouco mais fácil de demonstrar:
@EnableIntegration
@IntegrationComponentScan
public class SubflowsConfiguration {
@Bean
QueueChannel multipleOfThreeChannel() {
return new QueueChannel();
}
@Bean
QueueChannel remainderIsOneChannel() {
return new QueueChannel();
}
@Bean
QueueChannel remainderIsTwoChannel() {
return new QueueChannel();
}
boolean isMultipleOfThree(Integer number) {
return number % 3 == 0;
}
boolean isRemainderIOne(Integer number) {
return number % 3 == 1;
}
boolean isRemainderTwo(Integer number) {
return number % 3 == 2;
}
}
Por fim, é aqui que nossos números agrupados terminam.
Observe também que a integração do Spring pode facilmente começar a parecer complexa, então adicionaremos alguns métodos auxiliares para facilitar a leitura.
4. Resolvendo sem subfluxos
Agora precisamos definir nossos fluxos.
Sem subfluxos, a idéia simples é definir três fluxos de integração separados, um para cada tipo de número.
Enviaremos a mesma sequência de mensagens para cada componenteIntegrationFlow, mas as mensagens de saída para cada componente serão diferentes.
4.1. Definindo ComponentesIntegrationFlow
Primeiro, vamos definir cada beanIntegrationFlow em nosso sclassSubflowConfiguration :
@Bean
public IntegrationFlow multipleOfThreeFlow() {
return flow -> flow.split()
. filter(this::isMultipleOfThree)
.channel("multipleOfThreeChannel");
}
Nosso fluxo contém dois pontos finais - aSplitter seguido porFilter.
O filtro faz o que parece. Mas por que também precisamos de um divisor? Veremos isso em um minuto, mas basicamente, ele divide uma entradaCollection em mensagens individuais.
E podemos, é claro, definir mais dois grãosIntegrationFlow da mesma maneira.
4.2. Gateways de mensagens
Para cada fluxo, também precisamos de umMessage Gateway.
Simplificando, eles abstraem a API do Spring Integration Messages do chamador, da mesma forma que um serviço REST pode abstrair o HTTP:
@MessagingGateway
public interface NumbersClassifier {
@Gateway(requestChannel = "multipleOfThreeFlow.input")
void multipleOfThree(Collection numbers);
@Gateway(requestChannel = "remainderIsOneFlow.input")
void remainderIsOne(Collection numbers);
@Gateway(requestChannel = "remainderIsTwoFlow.input")
void remainderIsTwo(Collection numbers);
}
Para cada um, precisamos usar a anotação@Gateway annotation e especificar o nome implícito para o canal de entrada, que é simplesmente o nome do bean seguido por“.input”. Note that we can use this convention because we are using lambda-based flows.
Esses métodos são os pontos de entrada em nossos fluxos.
4.3. Envio de mensagens e verificação de saída
E agora, vamos testar:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { SeparateFlowsConfiguration.class })
public class SeparateFlowsUnitTest {
@Autowired
private QueueChannel multipleOfThreeChannel;
@Autowired
private NumbersClassifier numbersClassifier;
@Test
public void whenSendMessagesToMultipleOf3Flow_thenOutputMultiplesOf3() {
numbersClassifier.multipleOfThree(Arrays.asList(1, 2, 3, 4, 5, 6));
Message> outMessage = multipleOfThreeChannel.receive(0);
assertEquals(outMessage.getPayload(), 3);
outMessage = multipleOfThreeChannel.receive(0);
assertEquals(outMessage.getPayload(), 6);
outMessage = multipleOfThreeChannel.receive(0);
assertNull(outMessage);
}
}
Observe que enviamos as mensagens comoList, e é por isso que precisamos do divisor para pegar a única "mensagem de lista" e transformá-la em várias "mensagens numéricas".
Chamamosreceive como para obter a próxima mensagem disponível sem esperar. Como há dois múltiplos de três em nossa lista, esperamos poder chamá-lo duas vezes. A terceira chamada parareceive retornanull.
receive, of claro, retorna umMessage, então chamamosgetPayload para extrair o número.
Da mesma forma, poderíamos fazer o mesmo pelos outros dois.
So, that was the solution without subflows. Temos três fluxos separados para manter e três métodos de gateway separados.
O que faremos agora é substituir os trêsIntegrationFlow beans por um único bean e os três métodos de gateway por um único.
5. UsandopublishSubscribeChannel
O métodopublishSubscribeChannel() transmite mensagens para todos os subfluxos assinantes. Dessa forma, podemos criar um fluxo, em vez de três.
@Bean
public IntegrationFlow classify() {
return flow -> flow.split()
.publishSubscribeChannel(subscription ->
subscription
.subscribe(subflow -> subflow
. filter(this::isMultipleOfThree)
.channel("multipleOfThreeChannel"))
.subscribe(subflow -> subflow
. filter(this::isRemainderOne)
.channel("remainderIsOneChannel"))
.subscribe(subflow -> subflow
. filter(this::isRemainderTwo)
.channel("remainderIsTwoChannel")));
}
Desta forma,the subflows are anonymous, meaning that they can’t be independently addressed.
Agora, temos apenas um fluxo, então vamos editar nossoNumbersClassifier também:
@Gateway(requestChannel = "classify.input")
void classify(Collection numbers);
Agora,since we have only one IntegrationFlow bean and one gateway method, we need only send our list once:
@Test
public void whenSendMessagesToFlow_thenNumbersAreClassified() {
numbersClassifier.classify(Arrays.asList(1, 2, 3, 4, 5, 6));
// same assertions as before
}
Observe que a partir de agora, apenas a definição do fluxo de integração será alterada para que não mostremos o teste novamente.
6. UsandorouteToRecipients
Outra forma de conseguir o mesmo érouteToRecipients, o que é bom porque tem filtragem embutida.
Usando este método,we can specify both channels and subflows for broadcasting.
6.1. recipient
No código abaixo, especificaremosmultipleof3Channel,remainderIs1Channel,eremainderIsTwoChannel como destinatários com base em nossas condições:
@Bean
public IntegrationFlow classify() {
return flow -> flow.split()
.routeToRecipients(route -> route
. recipient("multipleOfThreeChannel",
this::isMultipleOfThree)
. recipient("remainderIsOneChannel",
this::isRemainderOne)
. recipient("remainderIsTwoChannel",
this::isRemainderTwo));
}
Também podemos chamarrecipient em uma condição erouteToRecipients publicará para esse destino incondicionalmente.
6.2. recipientFlow
E observe querouteToRecipients nos permite definir um fluxo completo, assim comopublishSubscribeChannel.
Vamos modificar o código acima e especificar umanonymous subflow as the first recipient:
.routeToRecipients(route -> route
.recipientFlow(subflow -> subflow
. filter(this::isMultipleOfThree)
.channel("mutipleOfThreeChannel"))
...);
This subflow will receive the entire sequence of messages, então precisamos filtrar como antes para obter o mesmo comportamento.
Novamente, um feijãoIntegrationFlow foi o suficiente para nós.
Agora vamos passar para os componentesif-else. Um deles éFilter.
7. Usando fluxosif-then
Já usamosFilter em todos os exemplos anteriores. A boa notícia é que podemos especificar não apenas a condição para processamento posterior, mas tambéma channel or aflow for the discarded messages.
Podemos pensar em fluxos e canais de descarte como um blocoelse :
@Bean
public IntegrationFlow classify() {
return flow -> flow.split()
. filter(this::isMultipleOfThree,
notMultiple -> notMultiple
.discardFlow(oneflow -> oneflow
. filter(this::isRemainderOne,
twoflow -> twoflow
.discardChannel("remainderIsTwoChannel"))
.channel("remainderIsOneChannel"))
.channel("multipleofThreeChannel");
}
Neste caso, implementamos nossa lógica de roteamentoif-else:
-
If o número não é um múltiplo de três,then descarta essas mensagens para o fluxo de descarte; we use a flow here since there is more logic needed to know its destination channel.
-
No fluxo de descarte,if o número não é do resto,then descarta essas mensagens para o canal de descarte.
8. switch-ing em um valor calculado
E, finalmente, vamos tentar o métodoroute , que nos dá um pouco mais de controle do querouteToRecipients. É bom porque umRouter pode dividir o fluxo em qualquer número de partes, enquanto uma varreduraFilter faça apenas dois.
8.1. channelMapping
Vamos definir nosso beanIntegrationFlow:
@Bean
public IntegrationFlow classify() {
return classify -> classify.split()
. route(number -> number % 3,
mapping -> mapping
.channelMapping(0, "multipleOfThreeChannel")
.channelMapping(1, "remainderIsOneChannel")
.channelMapping(2, "remainderIsTwoChannel"));
}
No código acima, calculamos uma chave de roteamento executando a divisão:
route(p -> p % 3,...
Com base nessa chave, roteamos as mensagens:
channelMapping(0, "multipleof3Channel")
8.2. subFlowMapping
Agora, como com outros, podemos ter mais controle especificando um subfluxo, substituindochannelMapping porsubFlowMapping:
.subFlowMapping(1, subflow -> subflow.channel("remainderIsOneChannel"))
Ou ainda mais controle chamando o smethodhandle em vez do smethodchannel :
.subFlowMapping(2, subflow -> subflow
. handle((payload, headers) -> {
// do extra work on the payload
return payload;
}))).channel("remainderIsTwoChannel");
Neste caso, o subfluxo retornaria ao fluxo principal após o métodoroute(), então precisaríamos especificar o canalremainderIsTwoChannel.
9. Conclusão
Neste tutorial, exploramos como filtrar e rotear mensagens de algumas maneiras usando subfluxos.
Como de costume, o código-fonte completo está disponívelon GitHub.