I've been playing around with Spring Integration (SI) DSL. I have a Rest service with the following Async Gateway defined :
#MessagingGateway
public interface Provision {
#Async
#Gateway(requestChannel = "provision.input")
ListenableFuture<List<ResultDto>> provision(List<ItemsDto> stuff);
}
From the Line-by-line walk-through I have the follow example IntegrationFlow.
#Bean
public IntegrationFlow provision() {
return f -> f
.split(ArrayList.class, List::toArray)
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.<ItemsDto, String>route(ItemsDto::getType, m -> m
.subFlowMapping("IPTV", sf -> sf
.<ItemsDto, String>route(ItemsDto::getAction, m2 -> m2
.subFlowMapping("OPEN", sf2 -> sf2
.handle((p, h) -> iptvService.open((ItemsDto) p))))
)
)
.aggregate();
}
I have several layers of routing as you can see. I need to break things up a bit. I've tried several things which don't work (here I don't get a response...the thread doesn't wait):
#Bean(name = "routerInput")
private MessageChannel routerInput() {
return MessageChannels.direct().get();
}
#Bean
public IntegrationFlow provision() {
return f -> f
.split(ArrayList.class, List::toArray)
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.<ItemsDto, String>route(ItemsDto::getType, m ->
m.subFlowMapping("IPTV", sf -> sf.channel("routerInput"))
)
.aggregate();
}
#Bean
public IntegrationFlow action() {
return IntegrationFlows.from("routerInput")
.<ItemsDto, String>route(ItemsDto::getAction, m -> m
.subFlowMapping("OPEN", sf -> sf
.handle(p -> iptvService.open((ItemsDto) p.getPayload())))).get();
}
I'm obviously conceptually missing something :) Can someone perhaps assist with a "how to and why" opinion?
I have a list of items that need to be split, routed by "type", then routed by "action", and finally aggregated (containing response of handler). Each handled item needs to process in parallel.
Thanks in advance
Update:
From Artem's suggestion I removed all the async stuff. I trimmed it down to almost nothing...
#Bean(name = "routerInput")
private MessageChannel routerInput() {
return MessageChannels.direct().get();
}
#Bean
public IntegrationFlow provision() {
return f -> f
.split()
.<ItemDto, String>route(ItemDto::getType, m ->
m.subFlowMapping("IPTV", sf -> sf.channel("routerInput")))
.aggregate();
}
#Bean
public IntegrationFlow action() {
return IntegrationFlows.from("routerInput")
.<ItemDto, String>route(ItemDto::getAction, m -> m
.subFlowMapping("OPEN", sf -> sf
.handle((p, h) -> iptvService.open((ItemDto) p)))).get();
}
I got it to respond by changing
.handle(p ->
to this
.handle((p, h) ->
So it at least responds, but it does not aggregate the 3 test items that were split. Output consists of 1 item. Do I need to use a stream collect? Release policy? Shouldn't this be fine?
It's probably simpler to use channelMapping rather than subflowMapping if you want to break it apart...
#Bean
public IntegrationFlow typeRoute() {
return IntegrationFlows.from(foo())
.split()
.<ItemsDto, String>route(ItemsDto::getType, m -> m
.channelMapping("foo", "channel1")
.channelMapping("bar", "channel2"))
.get();
}
#Bean
public IntegrationFlow fooActionRoute() {
return IntegrationFlows.from(channel1())
.<ItemsDto, String>route(ItemsDto::getAction, m -> m
.channelMapping("foo", "channel3")
.channelMapping("bar", "channel4"))
.get();
}
#Bean
public IntegrationFlow barActionRoute() {
return IntegrationFlows.from(channel1())
.<ItemsDto, String>route(ItemsDto::getAction, m -> m
.channelMapping("foo", "channel5")
.channelMapping("bar", "channel6"))
.get();
}
#Bean
public IntegrationFlow fooFooHandle() {
return IntegrationFlows.from(channel3())
// handle
.channel(aggChannel())
.get();
}
Create flows for the other options and aggregate each result:
// fooBarHandle(), barFooHandle(), barBarHandle()
#Bean IntegrationFlow agg() {
return IntegrationFlows.from(aggChannel())
.aggregate()
.get();
}
The parallelism is handled by using ExecutorChannels...
#Bean
public MessageChannel channel1() {
return new ExecutorChannel(exec());
}
#Bean
public MessageChannel channel2() {
return new ExecutorChannel(exec());
}
#Bean
public MessageChannel channel3() {
return new DirectChannel();
}
#Bean
public MessageChannel channel4() {
return new DirectChannel();
}
#Bean
public MessageChannel channel5() {
return new DirectChannel();
}
#Bean
public MessageChannel channel6() {
return new DirectChannel();
}
#Bean
public MessageChannel aggChannel() {
return new DirectChannel();
}
I don't see any big problems in your configuration and it really should work.
Want I don't like there is:
If you use async Gateway (ListenableFuture<List<ResultDto>>) you don't need #Async annotation there, because it will be already by the Gateway contract.
You don't need convert List::toArray if your payload is a List already. It is just enough to use .split() without params.
That's for the design style.
I'm not sure yet what's the problem there, but would you mind to make your whole flow sync and share with us here the DEBUG for the flow and point out where you see the problem.
Moved the aggregate to the "action" Bean and it worked.
Thanks for your patience :)
Related
I have the following flow:
#Bean
public IntegrationFlow workerInfoJmsOut(ConnectionFactory connectionFactory, WorkerProperties properties) {
return IntegrationFlows
.from(ChannelNames.WORKER_INFO)
.handle(Jms.outboundAdapter(connectionFactory)
.timeToLiveFunction(message -> properties.getHeartbeatTtl().toMillis())
.destination(JmsQueueNames.WORKER_INFO))
.get();
}
And the following gateway:
#MessagingGateway
public interface DispatcherGateway {
#Gateway(requestChannel = ChannelNames.WORKER_INFO)
void submit(WorkerMessage message);
}
Which is called every 1s:
#Scheduled(fixedDelay = 1000)
public void beat() {
dispatcherGateway.submit(WorkerInfoNotice.of(...));
}
However, the breakpoint in the timeToLiveFunction is never hit:
Did I misunderstand something or why is it never hit?
spring-integration-jms:5.3.1.RELEASE
I found out that I need to enable explicit QoS
#Bean
public IntegrationFlow workerInfoJmsOut(ConnectionFactory connectionFactory, WorkerProperties properties) {
return IntegrationFlows
.from(ChannelNames.WORKER_INFO)
.handle(Jms.outboundAdapter(connectionFactory)
.configureJmsTemplate(spec -> spec.explicitQosEnabled(true))
.timeToLiveFunction(message -> properties.getHeartbeatTtl().toMillis())
.destination(JmsQueueNames.WORKER_INFO))
.get();
}
I want do something like this where the gateway payload is a String and serviceA & serviceB both return lists.
final IntegrationFlow flowA = flow -> flow
.handle(serviceA)
.handle((payload, headers) -> payload); // List<Object>
final IntegrationFlow flowB = flow -> flow
.handle(serviceB)
.handle((payload, headers) -> payload); // List<Object>
return IntegrationFlows
.from(myGateway) // String payload
.forkAndMerge(flowA, flowB, executor)
.handle((payload, headers) -> payload)
.get();
Is it possible to fork the flow into two and then gather up the results? Most examples of splitter & aggregators involve splitting up a list.
See the .scatterGather() variants.
Main docs for the ScatterGatherer here.
EDIT
Example:
#SpringBootApplication
public class So63605348Application {
private static final Logger log = LoggerFactory.getLogger(So63605348Application.class);
public static void main(String[] args) {
SpringApplication.run(So63605348Application.class, args);
}
#Bean
IntegrationFlow flow(TaskExecutor exec) {
return IntegrationFlows.from(() -> "foo", e -> e.poller(Pollers.fixedDelay(5000)))
.scatterGather(s -> s.applySequence(true)
.recipientFlow(subFlow -> subFlow.channel(c -> c.executor(exec))
.<String>handle((p, h) -> {
log.info(p.toString());
return p.toUpperCase();
}))
.recipientFlow(subFlow -> subFlow.channel(c -> c.executor(exec))
.<String>handle((p, h) -> {
log.info(p.toString());
return p + p;
})))
.handle(System.out::println)
.get();
}
#Bean
public TaskExecutor exec() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
exec.setCorePoolSize(2);
return exec;
}
}
Result
2020-08-26 17:33:56.769 INFO 50829 --- [ exec-1] com.example.demo.So63605348Application : foo
2020-08-26 17:33:56.769 INFO 50829 --- [ exec-2] com.example.demo.So63605348Application : foo
GenericMessage [payload=[foofoo, FOO], headers=...
EDIT2
If you prefer not to nest the subflows, you can factor them out...
#Bean
IntegrationFlow flow(TaskExecutor exec) {
return IntegrationFlows.from(() -> "foo", e -> e.poller(Pollers.fixedDelay(5000)))
.scatterGather(s -> s.applySequence(true)
.recipientFlow(flow2())
.recipientFlow(flow3()))
.handle(System.out::println)
.get();
}
#Bean
IntegrationFlow flow2() {
return f -> f
.<String>handle((p, h) -> {
log.info(p.toString());
return p + p;
});
}
#Bean
IntegrationFlow flow3() {
return f -> f
.<String>handle((p, h) -> {
log.info(p.toString());
return p.toUpperCase();
});
}
Or you can use the pub/sub channel variant...
#Bean
IntegrationFlow flow() {
return IntegrationFlows.from(() -> "foo", e -> e.poller(Pollers.fixedDelay(5000)))
.scatterGather(pubSub())
.handle(System.out::println)
.get();
}
#Bean
PublishSubscribeChannel pubSub() {
PublishSubscribeChannel pubSub = new PublishSubscribeChannel(exec());
pubSub.setApplySequence(true);
return pubSub;
}
#Bean
IntegrationFlow flow2() {
return IntegrationFlows.from("pubSub")
.<String>handle((p, h) -> {
log.info(p.toString());
return p + p;
})
.get();
}
#Bean
IntegrationFlow flow3() {
return IntegrationFlows.from("pubSub")
.<String>handle((p, h) -> {
log.info(p.toString());
return p.toUpperCase();
})
.get();
}
#SpringBootApplication
#EnableIntegration
public class SpringIntegrationHttpApplication {
public static void main(String[] args) {
SpringApplication.run(SpringIntegrationHttpApplication.class, args);
}
#Bean
public HttpRequestHandlerEndpointSpec httpInboundAdapter() {
return Http
.inboundChannelAdapter("/failing-test")
.requestMapping(r -> r.methods(HttpMethod.GET)
.params("rObjectId"))
.payloadExpression("#requestParams.rObjectId[0]")
;
}
#Bean
public IntegrationFlow myFlow() {
return IntegrationFlows.from(Http
.inboundChannelAdapter("/test")
.requestMapping(r -> r.methods(HttpMethod.GET)
.params("rObjectId"))
.payloadExpression("#requestParams.rObjectId[0]"))
.transform(p -> p)
.handle(p -> System.out.println(p))
.get();
}
#Bean
public IntegrationFlow yourFlow() {
return IntegrationFlows.from(httpInboundAdapter())
.transform(p -> p)
.handle(p -> System.out.println(p))
.get();
}
}
For the above code, the link: /test works but not /failing-test.
I get "Endpoint is stopped" on chrome.
What might be the reason?
I am not sure why, yet, but remove the #Bean from the spec (just use a simple method that returns the Spec).
I'm trying to build a recipe for asynchronous orchestration using spring integration gateways (both inbound and outbound). After seeing an example here, I tried using scatter-gather like this:
#Configuration
public class IntegrationComponents {
#Value("${rest.endpoint.base}")
private String endpointBase;
#Bean
public HttpRequestHandlingMessagingGateway inboundGateway() {
return Http.inboundGateway("/test-inbound-gateway-resource")
.requestMapping(mapping -> mapping.methods(HttpMethod.POST))
.requestTimeout(3000)
.replyTimeout(3000)
.get();
}
#Bean
public HttpRequestExecutingMessageHandler outboundGateway1() {
return Http.outboundGateway(endpointBase + "/test-resource-1")
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.get();
}
#Bean
public HttpRequestExecutingMessageHandler outboundGateway2() {
return Http.outboundGateway(endpointBase + "/test-resource-2")
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.get();
}
#Bean
public StandardIntegrationFlow integrationFlow() {
ExecutorService executor = Executors.newCachedThreadPool();
IntegrationFlow flow1 = IntegrationFlows.from(MessageChannels.executor(executor))
.handle(outboundGateway1())
.get();
IntegrationFlow flow2 = IntegrationFlows.from(MessageChannels.executor(executor))
.handle(outboundGateway2())
.get();
return IntegrationFlows
.from(inboundGateway())
.transform(String.class, String::toUpperCase)
.channel(MessageChannels.executor(executor))
.scatterGather(
scatterer -> scatterer
.applySequence(true)
.recipientFlow(flow1)
.recipientFlow(flow2),
gatherer -> gatherer
.outputProcessor(messageGroup -> {
List<Message<?>> list = new ArrayList<>(messageGroup.getMessages());
String payload1 = (String) list.get(0).getPayload();
String payload2 = (String) list.get(1).getPayload();
return MessageBuilder.withPayload(payload1 + "+" + payload2).build();
}))
.get();
}
}
This executes, but my payloads are swapped, because in this case outboundGateway1 takes longer to execute than outboundGateway2. Payload 2 comes first, then payload 1.
Is there a way to tell scatter-gather to define/maintain order when sending to the output processor?
On a similar note, maybe split/aggregate and/or using a router is a better pattern here? But if so, what would that look like?
I tried the following split/route/aggregate, but it failed saying "The 'currentComponent' (org.springframework.integration.router.RecipientListRouter#b016b4e) is a one-way 'MessageHandler' and it isn't appropriate to configure 'outputChannel'. This is the end of the integration flow.":
#Configuration
public class IntegrationComponents {
#Value("${rest.endpoint.base}")
private String endpointBase;
#Bean
public HttpRequestHandlingMessagingGateway inboundGateway() {
return Http.inboundGateway("/test-inbound-gateway-resource")
.requestMapping(mapping -> mapping.methods(HttpMethod.POST))
.requestTimeout(3000)
.replyTimeout(3000)
.get();
}
#Bean
public HttpRequestExecutingMessageHandler outboundGateway1() {
return Http.outboundGateway(endpointBase + "/test-resource-1")
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.get();
}
#Bean
public HttpRequestExecutingMessageHandler outboundGateway2() {
return Http.outboundGateway(endpointBase + "/test-resource-2")
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.get();
}
#Bean
public StandardIntegrationFlow integrationFlow() {
ExecutorService executor = Executors.newCachedThreadPool();
IntegrationFlow flow1 = IntegrationFlows.from(MessageChannels.executor(executor))
.handle(outboundGateway1())
.get();
IntegrationFlow flow2 = IntegrationFlows.from(MessageChannels.executor(executor))
.handle(outboundGateway2())
.get();
return IntegrationFlows
.from(inboundGateway())
.transform(String.class, String::toUpperCase)
.split()
.channel(MessageChannels.executor(executor))
.routeToRecipients(r -> r
.recipientFlow(flow1)
.recipientFlow(flow2))
.aggregate()
.get();
}
}
Can you not simply Collections.sort() the list in the output processor? Each message will have a IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER header since you set applySequence.
How to configure a trigger in spring integration flow to get value from a method invoking message source and start it in another flow ?
#Bean
public IntegrationFlow integrationFlow() {
return IntegrationFlows.from(messageSource,channelSpec -> channelSpec.poller(Pollers.trigger(new SomeTrigger())).handle(...).get()
}
#Bean
public MessageSource<?> messageSource() {
MethodInvokingMessageSource source = new MethodInvokingMessageSource();
source.setObject(new Random());
source.setMethod("nextInt");
}
#Bean
public IntegrationFlow someOtherFlow() {
return IntegrationFlows.from("messageChannel")
///some logic to trigger and get the value of random int
}
The MessageSource has receive() method, so you can do just this:
#Bean
public MessageSource<?> randomIntSource() {
MethodInvokingMessageSource source = new MethodInvokingMessageSource();
source.setObject(new Random());
source.setMethodName("nextInt");
return source;
}
#Bean
public IntegrationFlow someOtherFlow() {
return IntegrationFlows.from("messageChannel")
.handle(randomIntSource(), "receive")
.handle(System.out::println)
.get();
}
pay attention to the .handle(randomIntSource(), "receive").