Spring AMQP with RabbitMQ receiving same message - java

I have small rabbitmq spring boot application:
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory)
{
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setAcknowledgeMode(AcknowledgeMode.AUTO);
return factory;
}
Simple queue definition:
#Bean
public Queue queue()
{
return new Queue("queue");
}
And a simple listener which logs the message:
#RabbitHandler
public void process(#Payload Message message)....
I am seeing that the same message is being sent multiple times before the
removed from queue.
How can i change this config to ensure the message is sent exactly once.

If your listener throws an exception the message will be requeued and sent.
It cannot be sent multiple times otherwise - impossible.
Turn on DEBUG logging to watch the message flow.

Related

Spring Integration: How to send messages from pubsub subscribers to external systems/servers with Http Methods

I have been trying to send messages to external systems(using rest template POST, PUT etc) from the service activators as below.
Below is my pubsub consumer class
public class MyConsumer{
#Autowired
ExternalService externalService;
#Bean
public PubSubInboundChannelAdapter messageChannelAdapter(final #Qualifier("myInputChannel") MessageChannel inputChannel,
PubSubTemplate pubSubTemplate)
{
PubSubInboundChannelAdapter adapter = new PubSubInboundChannelAdapter(pubSubTemplate, pubSubSubscriptionName);
adapter.setOutputChannel(inputChannel);
adapter.setAckMode(AckMode.AUTO_ACK);
adapter.setErrorChannelName("pubsubErrors");
return adapter;
}
#ServiceActivator(inputChannel = "pubsubErrors")
public void pubsubErrorHandler(Message<MessagingException> exceptionMessage) {
BasicAcknowledgeablePubsubMessage originalMessage = (BasicAcknowledgeablePubsubMessage) exceptionMessage
.getPayload().getFailedMessage().getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE);
originalMessage.nack();
}
#Bean
public MessageChannel myInputChannel() {
return new DirectChannel();
}
#Bean
#ServiceActivator(inputChannel = "myInputChannel")
public MessageHandler messageReceiver_AddCustomer() {
return message -> {
externalService.postDataTOExternalSystems(new String((byte[]) message.getPayload());
};
}
#Bean
#ServiceActivator(inputChannel = "myInputChannel")
public MessageHandler messageReceiver_DeleteCustomer() {
return message -> {
externalService.deleteCustomer(new String((byte[]) message.getPayload());
BasicAcknowledgeablePubsubMessage originalMessage =
message.getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE, BasicAcknowledgeablePubsubMessage.class);
originalMessage.ack();
};
}
}
ExternalService below is the service which sends data to the external systems.
public class ExternalService{
void postDataTOExternalSystems(Object obj){
// RequestEntity object formed with HttpEntity object using obj(in json) and headers
restTemplate.exchange("https://externalsystems/",HttpMethod.POST,requestEntity,Object.class);
}
void deleteDatafromExternalSystems(Object obj){
// RequestEntity object formed with HttpEntity object using obj(in json) and headers
restTemplate.exchange("https://externalsystems/",HttpMethod.Detele,requestEntity,Object.class);
}
}
Since both the methods messageReceiver_AddCustomer and messageReceiver_deleteCustomer are using same channel whats happening is when I try to just addcustomer, the deleteCustomer is also called by default.
I was thinking of creating a seperate channel for deleteCustomer, but creating in this way leads to creating channels for every usecase.
Hence would like to know three things here.
Is there is any other approach of sending through Spring integration through which I can send data to external systems using a single Channel or a different utilization of Channels.
If any error in the external service calls leads to unending of failure logs in the console
message_id: "6830962001745961"
publish_time {
seconds: 1675783352
nanos: 547000000
}
}, timestamp=1675783353720}]': error occurred in message handler
It's not clear what is your expectation for such a logic. You have two contradicting subscribers for the same input channel. It sounds more like you need a router first to determine where to proceed next with an input message from Pub/Sub: https://docs.spring.io/spring-integration/reference/html/message-routing.html#messaging-routing-chapter.
but creating in this way leads to creating channels for every usecase.
Sure! You can go without Spring Integration and just do everything with the plain if..else. So, what's a difference? Therefore I don't see a reasonable argument in your statement. Yo have different HTTP methods, and that is OK to map them via Spring Integration router to respective sub-flows. The MessageChannel is really first-class citizen in Spring Integration. It makes a solution as loosely-coupled as possible.
Several subscribers on your current DirectChannel will lead to a round-robin logic by default, so you'll be surprised that one message from Pub/Sub creates a customer, another deletes and so on in rounds. The PublishSubscribeChannel will make it bad as well: both of your subscribers are going to be called, so created first, then deleted immediately.

How to receive message from Spring Integration using Direct Channel

I have created DirectChannel and have sent an object to my channel and want to receive it abck to store it in DB and send it in another service bus queue. Can you suggest how to receive the object from channel?
My Channel -
#Bean("tempChannel")
public MessageChannel tempChannel() {
return new DirectChannel();
}
Integration flow -
#Bean
public IntegrationFlow tempMessageFlow() {
return IntegrationFlows.from("tempChannel").handle().get();
}
For handle method I need to pass MessageHandler, how to I declare it and pass here?
I am sending message to channel using below piece of code, please do tell if this is alright-
tempChannel().send(messageObj);
The DirectChannel implements a SubscribableChannel. So, to get messages sent to this channel you need to subscribe(MessageHandler handler). What you have so far with that IntegrationFlow definition is OK: adding that handle() you subscribe to the tempChannel. Just handle message and forget you can do this:
.handle(m - > System.out.println("Processed message: " + m))
This is a lambda for that MessageHandler functional interface. There are many other handle() variants for other use-cases. For example process-n-reply is like this:
.handle((p, h) - > {
System.out.println("Processed message: " + m);
return "My new payload";
})
If you say that you need to do several operations on the same message, then look into a PublishSubscribeChannel. In Java DSL we have a publishSubscribeChannel(Consumer<PublishSubscribeSpec> publishSubscribeChannelConfigurer) to configure several subscribers as sub-flows.
To store into DB, you can use a JdbcMessageHandler: https://docs.spring.io/spring-integration/docs/current/reference/html/jdbc.html#jdbc-outbound-channel-adapter

How to rollback in saga pattern when a service fails

I am starting with Saga pattern using Spring cloud and rabbit mq. Following is the problem statement:
I call /service1 (producer) which publishes a message in rabbit mq and that message is consumed by the consumer service.
Now occurs tow cases:
Case 1: Consumer service does its part successfully.
Case 2: Consumer service fails to do its part, thus /service1 has to rollback its changes.
How does /service1 know if consumer is successful or not, so that it can send a success/failure response. Following is the project structure:
Producer:
#RestController
public class ProducerController {
private MessageChannel greet;
public ProducerController(HelloBinding binding) {
greet = binding.greeting();
}
#GetMapping("/greet/{name}")
public void publish(#PathVariable String name) {
String greeting = "Hello, "+name+"!";
Message<String> msg = MessageBuilder.withPayload(greeting)
.build();
this.greet.send(msg);
System.out.println("Message sent to the queue");
AMQP.Basic.Ack;
}
Consumer:
#EnableBinding(HelloBinding.class)
public class HelloListener {
#StreamListener(target=HelloBinding.GREETING)
public void processHelloChannelGreeting(String msg) {
System.out.println("Message received:- "+msg);
}
}
Now how do I tell the producer whether consumer's action is a success or a failure so that producer service sends appropriate response?
The producer can not know what happens after a message has been successfully published to a topic. If you want feedback from the consumer then you need to create a new "response" topic on which the consumer communicates success or failure of processing that message.
You can map the messages by keys.

Race-condition between #SubscribeMapping and ChannelInterceptorAdapter.preSend

In my Spring Boot 1.5.9 application with Spring Websockets, web-socket subscriptions are intercepted with an implementation of ChannelInterceptorAdapter:
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHelper.accessor(message);
StompCommand command = accessor.getCommand();
if (null != command) {
StompHelper.authentication(message, tokenHandler);
switch (command) {
case SUBSCRIBE:
this.stompHandler.subscribe(message);
break;
case UNSUBSCRIBE:
this.stompHandler.unsubscribe(message);
break;
// ...
}
}
return super.preSend(message, channel);
}
which captures the subscriber and destination so subscriber-specific messages can be sent. In the web-socket controller, the #SubscribeMapping implementation publishes initialization data to the subscriber. But the #SubscribeMapping and ChannelInterceptorAdapter.preSend are invoked in different threads, so occasionally the #SubscribeMapping executes before the completion of ChannelInterceptorAdapter.preSend which results in the subscriber not getting the initialization data.
This seems like a Spring Websocket design problem, but is there an (elegant-ish) work-around?
Update: submitted a bug: SPR-16323

Use #SentTo to send a message with Spring Boot and RabbitMq

Is it possible to send a return value of any method to a queue using an annotation, like
#SentTo("my.queue.name")
String send() {
return myString;
}
Do I definitely need a #RabbitListener to use #SendTo? Maybe another way out?
I'm trying to simplify my code.
#SendTo is only currently for replies from a #RabbitListener where the sender didn't set a replyTo header.
You could do what you want with a Spring Integration #Publisher annotation with its channel wired to a rabbitmq outbound channel adapter...
#Publisher(channel = "amqpOutboundChannel")
public String send() {
return myString;
}
#Bean
#ServiceActivator(inputChannel = "amqpOutboundChannel")
public AmqpOutboundEndpoint amqpOutbound(AmqpTemplate amqpTemplate) {
AmqpOutboundEndpoint outbound = new AmqpOutboundEndpoint(amqpTemplate);
outbound.setRoutingKey("my.queue.name"); // default exchange - route to queue 'my.queue.name'
return outbound;
}
The method has to be public and invoked from outside the bean itself.

Categories