I have set up a simple Spring Integration flow which is composed of such steps:
poll a rest api periodically then
do some processing on the payload
and land it on a Kafka topic.
Please observe the code below:
#Component
public class MyIntegrationFlow extends IntegrationFlowAdapter {
#Override
protected IntegrationFlowDefinition<?> buildFlow() {
return from(() -> List.of("pathVariable1", "pathVariable2"), c -> c.poller(Pollers.fixedDelay(5, TimeUnit.SECONDS)))
.split()
.handle(httpRequest(), c -> c.advice(new RequestHandlerRetryAdvice()))
.transform(Tranformers.fromJson(Foo.class))
.filter(payload -> payload.isValid())
.log()
.transform(Tranformers.toJson())
.channel(Source.OUTPUT); // output channel for kafka topic
}
private HttpMessageHandlerSpec httpRequest() {
return Http.outboundGateway("http://somehost:8080/{pathVariable}")
.httpMethod(GET)
.uriVariable("pathVariable", Message::getPayload)
.expectedResponseType(String.class);
}
}
This works brilliantly, however, I am struggling to come up with some good tests.
How am I supposed to mock the external REST API?
How am I supposed to test that the retry policy does kick in and the desired number of http requests are made?
How do I change the MessageSource of the flow (list of path vars) that is polled periodically?
How do I check if the payload has successfully made it to the Kafka topic?
Too much questions and some of them requires too broad explanation. Anyway I think you can start from Spring Integration Testing Framework and its documentation.
How am I supposed to mock the external REST API?
I think you can just consider to use a Mock MVC from Spring Framework and its MockMvcClientHttpRequestFactory to inject into the HttpRequestExecutingMessageHandler based on the HttpMessageHandlerSpec.
retry policy does kick
Well, I guess the same mocked MVC endpoint can verify how many times it has been called and fail for first several times to initiate that retry.
How do I change the MessageSource
This is exactly a part of Spring Integration Testing Framework with its MockIntegration.mockMessageSource() and MockIntegrationContext: https://docs.spring.io/spring-integration/docs/5.1.6.RELEASE/reference/html/#mockintegration
made it to the Kafka topic?
Or you the mentioned MockIntegration.mockMessageHandler() to verify that endpoint for Kafka is called. Or use an Embedded Kafka from Spring Kafka project: https://docs.spring.io/spring-kafka/docs/2.2.7.RELEASE/reference/html/#embedded-kafka-annotation
Related
I have to listen a queue using spring integration flow and intgeration sqs. Once message is received from queue it should trigger a integration flow. Below is the things which I am trying but everythings fine in but afater receiving test it is not triggering any Integration flow. Please let me know where I am doing wrong:
UPDATED as per comment from Artem
Adapter for SQS.
#Bean
public MessageProducerSupport sqsMessageDrivenChannelAdapter() {
SqsMessageDrivenChannelAdapter adapter = new SqsMessageDrivenChannelAdapter(amazonSQSAsync, "Main");
adapter.setOutputChannel(inputChannel());
adapter.setAutoStartup(true);
adapter.setMessageDeletionPolicy(SqsMessageDeletionPolicy.NEVER);
adapter.setMaxNumberOfMessages(10);
adapter.setVisibilityTimeout(5);
return adapter;
}
Queue configured:
#Bean
public MessageChannel inputChannel() {
return new DirectChannel();
}
Now the main integration flow trigger point:
#Bean
public IntegrationFlow inbound() {
return IntegrationFlows.from("inputChannel").transform(i -> "TEST_FLOW").get();
}
}
Appreciate any type of help.
The sqsMessageDrivenChannelAdapter() must be declared as a #Bean
The inbound() must be declared as a #Bean
This one fully does not make sense IntegrationFlows.from(MessageChannels.queue()). What is the point to start the flow from anonymous channel? Who and how is going to produce messages to that channel?
Make yourself familiar with different channels: https://docs.spring.io/spring-integration/docs/current/reference/html/core.html#channel-implementations
Pay attention that QueueChannel must be consumed via polling endpoint.
Right, there is a default poller auto-configured by Spring Boot, but it is based on a single thread in the TaskScheduler and has a polling period as 10 millis.
I wouldn't recommend to hand off SQS messages to the QueueChannel: when consumer fails, you lose the data. It is better to process those messages in the consumer thread.
Otherwise your intention is not clear in the provided code.
Can you, please, share with us what error you get or anything else?
You also can turn on DEBUG logging level for org.springframework.integration to see how your messages are processed.
My beans/listeners are built using IntegrationFlows
ex.
#Bean
IntegrationFlow registerDevices() {
return IntegrationFlows
.from(adapter)
.channel(channel)
.get();
}
there is such a way to stop listeners after something happens, endpoint call/event
RabbitListenerEndpointRegistry does not work, because I'm not using #RabbitListener
There is the possibility to set autostart up - false, but then how to manipulate it nicely during runtime?
First of all, please, be careful when you choose tags for your questions over here. You concern so far really belongs to Spring Integration and has nothing to do with Spring AMQP. Although you are right: the lifecycle control really goes in the end to the ListenerContainer from Spring AMQP.
Anyway: the end-user API for your use-case is a part of Spring Integration logic.
See the second argument of that from():
.from(adapter, e -> e.id("myAmqpAdapter"))
So, having that id yo are able to reach your AmqpInboundChannelAdapter (implement a Lifecycle) at runtime and stop & start it whenever you need.
See more in docs:
https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-endpoints
And this pattern is also good to have on board:
https://docs.spring.io/spring-integration/docs/current/reference/html/system-management.html#control-bus
I am trying to configure integration flow with gateway. Using Java DSL on kotlin.
Gateway config:
#MessagingGateway(name = "tdiOutSenderGateway")
interface TdiOutSenderGateway {
fun send(packet: PhasorEnricher.Packet)
}
Flow config:
#Bean
open fun tdiOutSendFlow() = IntegrationFlows
.from(TdiOutSenderGateway::class.java)
.transform(tdiOutSenderRouter())
.get()!!
got send is not supported, because no request channel has been configured
docs: request channel will be autoconfigured.
#IntegrationComponentScan exists
https://docs.spring.io/spring-integration/reference/htmlsingle/#java-dsl-gateway readed and used as base
double checked: I am using exactly this gateway
Is there any additional setting I missed?
Of course, transform should return something, there should be route or handle.
But even I fixed #1 I faced with that: Void kotlin functions return Unit. Spring integrations checks Unit == null which is false, tries to find the next channel and throw an error. The fix is to use kotlin lambda and return null explicitly.
After working with Spring Integration and kotlin these 8 months I decided to try to create Kotlin DSL for Spring Integration (https://github.com/spring-projects/spring-integration/issues/3016)
Kotlin Unit: https://kotlinlang.org/docs/reference/functions.html#unit-returning-functions
P.S.: Of course I solved it earlier, not after 8 months.
I need to throttle the movement of messages between some JMS (activeMQ) queues to ensure I dont overrun an external service used during message processing.
I have done this in the past with Camel but given that this project is otherwise entirely Spring based, I figured I'd give spring-integration a whirl.
I am happy to see that in 5.0.7 the Java DSL is in core and would really like to use it instead of xml.
But...I cant seem to find good/current docs for using the DSL to do even simple things like create the input and output messageChannels for JMS.
Could anyone point me to any current example of using the java DSL to create channels that I can use to consume and produce messages with...and then later use in a bridge with some throttling applied?
Well, looks like our JMS chapter in the Reference Manual leaks of the Java DSL samples, similar to what we have so far with AMQP, for example:
https://docs.spring.io/spring-integration/docs/5.0.7.RELEASE/reference/html/amqp.html#_configuring_with_the_java_dsl_2
https://docs.spring.io/spring-integration/docs/5.0.7.RELEASE/reference/html/amqp.html#_configuring_with_the_java_dsl_4
I believe we can add similar paragraphs into the JMS chapter as well. Please, raise a JIRA on the matter and we will address it soon. Meanwhile I suggest you to open a org.springframework.integration.jms.dsl.Jms factory for appropriate builder to use.
On the other hand I can suggest you to take a look into the existing test-case of some possible configurations: https://github.com/spring-projects/spring-integration/blob/master/spring-integration-jms/src/test/java/org/springframework/integration/jms/dsl/JmsTests.java
For example to read from the queue you need a configuration like this:
#Bean
public IntegrationFlow jmsMessageDrivenFlow() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(jmsConnectionFactory(), DefaultMessageListenerContainer.class)
.outputChannel(jmsMessageDrivenInputChannel())
.destination("jmsMessageDriven")
.configureListenerContainer(c -> c.clientId("foo")))
.<String, String>transform(String::toLowerCase)
.channel(jmsOutboundInboundReplyChannel())
.get();
}
To send to the JMS you need something like this:
#Bean
public IntegrationFlow jmsOutboundFlow() {
return f -> f
.handle(Jms.outboundAdapter(jmsConnectionFactory())
.destinationExpression("headers." + SimpMessageHeaderAccessor.DESTINATION_HEADER)
.configureJmsTemplate(t -> t.id("jmsOutboundFlowTemplate")));
}
This is regarding the issue I am facing to test the service activator. Technologies used are - Spring Integration with Redis - using RedisQueueOutboundChannelAdapter and RedisQueueMessageDrivenEndpoint.
The application code can be found at link https://github.com/SRekha-LV/SISamples/tree/master/SpringRedisTestEg
Flow - HomeController sends asynchronous messages to the Service Activator method - processQueue1Details () in class ProcessQueue1Messages.
The intention is to test the no. of times the method - processQueue1Details () is being called. The sample testcase for the same is in the test class - TestHitCount.java - testProcessQueue1 ().
Executing this is giving an exception "Wanted but not invoked:
processQueue1Messages.processQueue1Details(
GenericMessage [payload=TEst, headers={timestamp=1457436349427, id=d1b622d4-43b1-dfc4-e114-2ae700cbdb6c}]
);
-> at com.spring.mvc.redis.TestHitCount.testProcessQueue1(TestHitCount.java:42)
Actually, there were zero interactions with this mock."
Need some help.
Thanks in advance for all the support.
Your application is very complex and your question is enough dirty. That's why someone has de-voted your question already.
I really can't catch your your configs and test, but I have two advises:
In Spring Integration you should use #ServiceActivator for MessageHandler #Beans. To be able to consume from the channel and handle message, e.g. send it to Redis List like all those your RedisQueueOutboundChannelAdapters.
If you test the async scenario you have to wait for the result on the other hand before invoke the .verify(). E.g. in your case you send to the Redis and exit. There is really no any interaction with your mock.
Let's take a look to your code closer!
#Test
public void testProcessQueue1 () {
ProcessQueue1Messages pQueue1Msgs = Mockito.mock(ProcessQueue1Messages.class);
Object strObj = "TEst";
Message<Object> message = MessageBuilder.withPayload(strObj).build();
rulesRQOutboundChannelAdapter.handleMessage(message);
Mockito.verify(pQueue1Msgs).processQueue1Details(message);
}
You create mock in the test and nowhere provide it. So how your Spring Application should interact with the mock, if it is just local variable?