SyntaxHighlighter

Wednesday, July 17, 2013

Spring Integration, ActiveMQ Dead Letter Queue and Testing

Spring Integration, ActiveMQ Dead Letter Queue and Testing


recently i was working on a design whereby the application would consume messages from a JMS queue, but may encounter a transient exception (e.g. the database connection may not be available temporarily).  in Spring Integration i could use a retry pattern on the endpoint, but as the application is in a cluster, another JVM may have better luck getting to the database.  so, to use the cluster design, if a transient exception should occur, the exception would be thrown up, the transaction would roll back and the message would go back on the queue, awaiting consumption again.  this would allow another node in the cluster an opportunity to get the message and process it.

ok, so this is all very nice, but how do we confirm that this actually works?  we're using ActiveMQ in the test environment (embedded) and wanted to see the message actually got rolled back.  the default behavior of ActiveMQ, (since version 5.4?) is the following;

  • no delay in retries
  • 6 attempts
  • if all attempt fail, put it on the Dead Letter Queue
so, here's a couple of snippets that use this to test.

first the integration setup;



 
  
 
 
 

 

 
  
  
  
  
 
 



***note: a DefaultMessageListenerContainer is manually configured to support the transactions, setting the transactionManager directly on the channel adapter alone doesn't work (in 2.2.4.RELEASE)

now our test resources;



 

  
   
  
  
  
   
  
  
  
   
  
  
  
      
  
   
  
  
  
      
  
  
  
      
      
  
 
 



our testing bean;

package de.incompleteco.spring.integration.service;

public class FailureService {

 public void process(Object payload) {
  throw new RuntimeException("simulated failure");
 }
 
}

and our junit test;

package de.incompleteco.spring.integration;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import javax.jms.ConnectionFactory;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.TextMessage;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath*:/META-INF/spring/*-context.xml"})
@ActiveProfiles("junit")
public class DeadLetterQueueIntegrationTest {

 private static final String PAYLOAD = "hello world";
 
 @Autowired
 private ConnectionFactory connectionFactory;
 
 @Autowired
 @Qualifier("request.queue")
 private Queue requestQueue;
 
 @Autowired
 @Qualifier("dead.letter.queue")
 private Queue dlQueue;
 
 private JmsTemplate jmsTemplate;
 
 @Before
 public void before() throws Exception {
  jmsTemplate = new JmsTemplate(connectionFactory);
  jmsTemplate.setDefaultDestination(dlQueue);
  jmsTemplate.setReceiveTimeout(2 * 1000);//about 2 seconds
 }
 
 @Test
 public void test() throws Exception {
  //send a message
  jmsTemplate.convertAndSend(requestQueue, PAYLOAD);
  //expect it to fail and, by default, be put back on the dead letter queue
  Message failedMessage = jmsTemplate.receive();
  //now check that it has the same content
  assertTrue(failedMessage instanceof TextMessage);
  assertEquals(PAYLOAD,((TextMessage) failedMessage).getText());
 }
 
}


3 comments:

  1. My friend got the whole code, please can Share? greetings from Chile.

    miloparragonzalez@gmail.com ---> this is my mail

    ReplyDelete
  2. How does the spring know that failure message should go to DLQ? There is no configuration.. Can you send me the code please

    ReplyDelete
  3. Looks like the example is incomplete. Could you provide the full example.

    ReplyDelete