0

In my application a user creates a post which gets persisted in the db + published to a Spring amqp queue

When the user creates a post flow hits the controller

@RequestMapping(value="/createPost", method=RequestMethod.POST, consumes = "application/json", 
            produces = "application/json")
    public @ResponseBody Post createUserPost(@RequestBody(required=false) Post post, Principal principal){
        this.userService.persistPost(post);
        logger.info("post persistance successful");
        publishService.publishUserPosts(post);
        return post;
    }

There are two services persistPost & publishUserPosts in different Service classes called in the controller.

Publish Service

@Transactional
    public void publishUserPosts(Post post){
        try{
            logger.info("Sending user post to the subscribers");
            amqpTemplate.convertAndSend(post);
            }catch(AmqpException e){
                throw new MyAppException(e);
            }
    }

The problem is both service calls are running under different transactions. If the PublishPost transaction fails the post is still persisted in the db.

To bring both the services under a single transaction I've changed the code & injected the persistPost service in PublishPost class.

@Transactional
    public void publishUserPosts(Post post){
        try{
            userService.persistPost(post);
            logger.info("post persistance successful");
            logger.info("Sending user post to the subscribers");
            amqpTemplate.convertAndSend(post);
            }catch(AmqpException e){
                throw new MyAppException(e);
            }
    }

My question

Is this the best approach to achieve multiple services under a single transaction or I can do better with some other approach?

underdog
  • 4,447
  • 9
  • 44
  • 89
  • Just to be sure, are you using JTA? Otherwise the transaction you are using to send the message is not really doing anything. – Augusto Oct 17 '15 at 20:47
  • I am using the HibernateTransactionManager. I have tested the code, transaction works fine, just need to confirm the approach. – underdog Oct 18 '15 at 06:45

1 Answers1

1

I think you're confused about how transactions works. The HiberanteTransactionManager can only deal with database operations. To make other parts transactional too, such as messaging, you must use technology called Java Transactional API (JTA), whichs allows to merge transactions of different technologies into one big distributed transaction. In Spring, this is provided by the JTATransactionManage.

Regardless of the above, the more accepted design in this case (if you follow Domain-Driven Design patterns) is to have one Application Service which acts as a facade to your domain and is responsible for keeping the transactional boundary. This Application Service then calls the Post Repository (what you called userService) and finally publishes the message. So in pseudo code

class PostApplicationService {
   @Transactional
   public void publishUserPosts(Post post){
      postRepository.save(post);
      publishService.notifyNewPost(post);
   }
}

And I would do the notification using an local event bus (such as the one provided by Spring or Guava). But that is just my preference :).

Augusto
  • 28,839
  • 5
  • 58
  • 88
  • Thanks for the reply Augusto, I have implemented transactions as per the Spring AMQP doc, RabbitMQ doesn't support distributed transactions. So I am following the Best effort 1PC one phase commit pattern, using the hibernate transaction manager. I've even checked in the logs, the message sender & listener container are covered by the HibernateTransactionManager. As per the docs @Transactional works fine with messaging its just we require a channel supporting transactions. Also I am thinking of using server sent events for sending the notification for a new post to the browser. – underdog Oct 18 '15 at 15:09