0

We have a large application where emails are sent as part of all the Services' @Transactions. A typical Service method is

@Transactional(readOnly = false, rollbackFor = {Exception.class})
public String operation() {
     //...
     sendEmail(..); // will call JavaMailSender.send()
     //...
     if (condition) {
       sendEmail(..); // will call JavaMailSender.send()
     }
}

The mail server often has issues which cause the whole transaction to get rolled back. We need to take the emailing out of the transactions. It's not feasible to move these lines one-by-one out of all the Services, or change the rollbackFor annotation for each, due to the size of the existing app. I was thinking of adding @Async to the custom sendEmail method but doing that inside a transaction is not recommended and can still impact the transaction.

Some options under consideration are:

  • Use a message queue e.g. RabbitMQ in Spring. The method sendMail can be tweaked to enqueue a serialized mail object with params, and a listener will consume. But this requires opening a new port for MQ
  • A microservice: also requires opening a new URL and port
  • A cron that runs every minute and processes incomplete rows entered by a tweaked sendMail that just adds table rows.

But is there a simpler, centralized robust solution that doesn't require all this new architecture? The actual emailing simply has to be outside all transactions.

gene b.
  • 10,512
  • 21
  • 115
  • 227
  • You could use an `ExecutorService` to handle to sending of the mail. Your main thread utilising the transactions would simply hand off processing to another thread. – Garreth Golding Dec 21 '22 at 17:24
  • cf. Spring `@Async` annotation – akuma8 Dec 21 '22 at 17:30
  • But will `ExecutorService` conflict with or block `Transaction`s? The results are not well-defined when using Async operations inside a Transaction, according to what M. Deinum wrote in that thread I linked to in the OP. – gene b. Dec 21 '22 at 18:15

1 Answers1

0

TL;DR:

Use polymorphic behavior to change your class/interface that sends emails to do something different. For instance you could add all your emails to a Queue and then have a different service behind the scenes send those emails. Getting around the transaction because a different thread will be sending the emails.

Longer Answer

In the longest long run, I would urge you to consider removing the email code from your transactions. Based on your question, that sounds like a lot of work, but in the long run, it will be better. Generally speaking, sending an email is not part of the transaction. BTW, this change doesn't have to happen all at once. Make a ticket in your system that contains all the transactions that has this 'problem' and a way of fixing this problem. When folks get a bit of developer time, they could possibly chose one of the methods on the list and fix it.

In the short run, you could implement a new implementation of your email class and have that class place all your emails on a queue. We use Spring Integration for our queuing framework and we have liked it quite a bit.

In the medium run, always use a queuing strategy to send emails. The reading side of the queue should only send out a certain number of emails a second/minute/hour based on your email servers rules and capacity. That way if something goes wrong in your app and for some reason it starts send our hundreds of thousands of emails in an hour due to a 'random' security probe that happens on your day off you won't get a call from your divisions director 'asking' you to fix the problem.

hooknc
  • 4,854
  • 5
  • 31
  • 60