1

In Spring, Rabbitmq supports transactions. However, Amazon sqs does not support transactions in Spring.

I'm sorry. I added some more content. I tested two message queues(rabbitmq, amazon sqs) as shown below in spring.

My purpose is the logic to process the user email to the queue to send the signup completion email when the user completes the signup without exception.

//rabbit mq configuration.class
@Bean
public ConnectionFactory rabbitConnectionFactory() {
    CachingConnectionFactory connectionFactory =
            new CachingConnectionFactory("localhost",5672);
    connectionFactory.setUsername("guest");
    connectionFactory.setPassword("guest");
    return connectionFactory;
}

@Bean
public SimpleMessageListenerContainer messageListenerContainer() {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(rabbitConnectionFactory());
    container.setQueueNames(queue);
   container.setMessageListener(exampleListener());
    container.setTransactionManager(platformTransactionManager);
    container.setChannelTransacted(true);
    return container;
}


@Bean
public RabbitTemplate producerRabbitTemplate() {
    RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory());
    rabbitTemplate.setQueue(queue);
    rabbitTemplate.setMandatory(true);
    rabbitTemplate.isChannelTransacted();
    rabbitTemplate.setChannelTransacted(true);
    return rabbitTemplate;
}



//UserService.class
@Autowired
private final UserRepository userRepository;
@Autowired
private final RabbitTemplate rabbitTemplate;

@Transactional
public User createUser(final User user){
    rabbitTemplate.convertAndSend("spring-boot", user.getEmail()); // SignUp Completion email
    final User user = userRepository.save(user);
    if(true) throw new RuntimeException();
    return user;
}

However, the logic above occurs a runtimeException.

Rabbit mq will not send an data to queue due to transactional annotation in Spring if an exception occurs.

//amazon sqs configuration.class
@Bean
public QueueMessagingTemplate queueMessagingTemplate(AmazonSQSAsync amazonSqs) {

//UserService.class
@Autowired
private final UserRepository userRepository;
@Autowired
private QueueMessagingTemplate messagingTemplate;


@Transactional
public User createUser(final User user){
    messagingTemplate.convertAndSend("spring-boot", user.getEmail()); // SignUp Completion email
    final User user = userRepository.save(user);
    if(true) throw new RuntimeException();
    return user;
}

However, sqs will send data to the queue even if an exception occurs.

Does anyone know why this is?

ansatgol
  • 155
  • 4
  • 13
  • 2
    SQS is literally just a message queue. Can you provide an example of what problem you're trying to solve, and how you think a "transaction" would help? From there we can help you solve that problem. – Krease Oct 22 '18 at 22:08
  • @Krease Can you check content in above ? – ansatgol Oct 23 '18 at 16:58

2 Answers2

3

TLDR How can I solve this problem?

Don't try to use transactions for this, come up with some way to make the system eventually consistent.


It sounds like you want to perform a 'queue.sendMessage' and 'repository.save' as if they were a transaction - either both get committed, or none get committed. The problem is that the 'transaction' isn't really transactional, even in your rabbit example.

Consider the basics of how a transaction works:

  1. some sort of begin step, that signifies following changes are part of the transaction
  2. changes are made (but not visible to readers)
  3. some sort of commit step to atomically commit the changes (making them visible to readers)

However, in your case, the queue and the repository are separate entities, backed by separate network resources, that don't talk to each other. There is no atomic commit in this case. Without an atomic commit, you cannot have a true transaction. It "works" in your demo because the exception is separate from the code that is doing the write.

Consider this case to more clearly illustrate:

@Transactional
public User createUser(final User user){
    messagingTemplate.convertAndSend("spring-boot", user.getEmail());
    final User user = userRepository.save(user);
    return user;
}
  • When commit step starts for this, it needs to make visible both the message (in the queue) and the user (in the repo)
  • In order to do this, a network call needs to be made to both the queue and the repo
    • the fact that these are two calls is the source of the problem
  • If one succeeds and the other fails, you end up in an inconsistent state
    • you may say "transactions can be rolled back" - but rolling back would involve another network call, which of course can fail.

There are ways to make the overall distributed system transactional, but it's a very complex problem. It's often much easier and faster to allow for temporary inconsistency and have mechanisms in place to make the system eventually consistent.

Krease
  • 15,805
  • 8
  • 54
  • 86
  • On top of the solutions listed in the linked article, you can also use event sourcing to solve this issue. It provides you strong consistency guarantees on writes and eventual consistency on reads. Though it comes with its own complexity. – Melkis H. Jul 20 '22 at 23:13
0

Transactional outbox pattern is also a solution to achieve eventual consistency. Details here: https://microservices.io/patterns/data/transactional-outbox.html

cris
  • 29
  • 3