Sending events at the end of a transaction

前端 未结 3 971
闹比i
闹比i 2021-02-08 06:50

I have an interface for a Service object that looks something like the following (simplified for brevity):

public interface ItemService {

  public Item getItemB         


        
相关标签:
3条回答
  • 2021-02-08 07:08

    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:

    • send the events and "store" them in a list in your event handling mechanism, but do not forward the events to the recivers. (I guess that is easy to implement)
    • forward the events from the list to the recviers if the transaction was sucessfull
    • delete the events from the list if the transaction has a roll-backed

    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.

    0 讨论(0)
  • 2021-02-08 07:13

    As @Wouter mentioned one alternative is using asynchronous messaging techniques. I can think of 2 other approaches:

    1. the events fired by 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.
    2. Use a ThreadLocal to temporarily queue all the events and after the composite transactions commited, fire the events. This is not persistent and may fail, e.g. after commit and before all events have been dequeued. I'm not the fan of ThreadLocal but it's better than messing the business interfaces (ItemService) with non-related parameters. See this for spring related implementation.

    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.

    0 讨论(0)
  • 2021-02-08 07:27

    Summarizing your question: You're looking for a transactionally-safe way to send messages.

    Option 1: JMS

    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.

    Option 2: Transaction synchronization

    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:

    • if an exception is thrown in the handling of the events in this case, your service will throw the exception, but the changes will already have been committed in the database.
    • if one of your listeners also writes into the database, it will have to do so in a new transaction.

    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.

    0 讨论(0)
提交回复
热议问题