I have an interface for a Service object that looks something like the following (simplified for brevity):
public interface ItemService {
public Item getItemB
In contrast to Wouter Coekaerts I understand that you are asking for an way to send notifications that will be only submitted to the reciver if the transaction in which they are created was succesfull. -- So you are looking for something that is similar to the transactional CDI-Event mechanism.
My idea to solve it is this way:
To forward or delete the events I would first have a lookt at the spring transaction mechanism. If there is no way to extend it, you could write an AOP aspect that forward the events if a method annotated with @Transactional
was leaving wihtout an (Runtime)Exception. But if the @Transactional
annotated Method was leaving with an (Runtime)Exception then delete the events from the list.
As @Wouter mentioned one alternative is using asynchronous messaging techniques. I can think of 2 other approaches:
ItemService
get stored in database table after commit (so on rollback they are not avialable). A background (async) job examines the events and calls the appropriate services. You could call it 'selfmade JMS'. Possible alternative if messaging infrastructure is not available.I guess a final answer is not possible as it really depends on the details of your system, especially all the external services that get triggered by the events.
Summarizing your question: You're looking for a transactionally-safe way to send messages.
Transactionally safe messaging is exactly what JMS is for. There's also good JMS integration in Spring, See the JMS chapter in the Spring documentation.
That will make sure that the messages are sent if and only if the transaction is committed. It also helps for dealing with errors in listeners for these events.
A difference with your current setup is that these events will be handled asynchronously: your service will return before these events have been handled. (JMS will make sure that they are processed eventually, it can be configured to try multiple times and how to deal with errors,...). Depending on your needs, that may be a good or a bad thing.
Alternatively, if JMS is too heavy-weight for your case, you could use transaction synchronization: When sending an event, instead of sending it directly use Spring's TransactionSynchronizationManager.registerSynchronization
, and send the message in the afterCommit()
of your TransactionSynchronization
.
You could either add a new synchronization per event to be sent, or add one synchronization and keep track of which events to be sent by binding an object containing that list to the transaction using TransactionSynchronizationManager.bindResource
.
I would advise against trying to use your own ThreadLocal
for this, because that would go wrong in some cases; for example if inside your transaction you would start a new transaction (RequiresNew
).
Differences with your current setup:
Alternatively, you can use beforeCommit
instead of afterCommit
, but then your events will be handled (mails sent,...) even if the actual commit to the database later fails.
This is less robust (less transactional), than using JMS, but lighter and easier to set up, and usually good enough.