After reading Vaughn Vernon's Implementing Domain-DrivenDesign I decided to start the fourth redesign of an application that I developed. Just to get some experience with command and event based architectures. This article is about the transactional applicationservice layer. First I describe how I use multiple Spring transactions in one service call. Then I share how I discovered how to use the proper transaction propagation mode. Auteur: Bart Swennenhuis
Manage multiple transactions in one request (keywords: Spring declarative transaction domain event ddd domaindriven aggregate)
Why do I need this in the first place, why not use just one transaction? According to the Domain Driven Design principles transactions should not cross aggregate boundaries. Updating an aggregate could emit an event that needs to be processed by another aggregate in the same bounded context. If we keep things simple we let the same thread handle the emitted events. So we need a second transaction for the event processing. To be clear, I am not talking about a nested transaction, it's one transaction followed by another. In this example I use two aggregates. FirstAggregate produces a domain event.SecondAggregate is interested in this event to update its state. This diagram shows the basic idea:
I'll start with the event processing to update SecondAggregate. Then I work my way backto the application service that updates FirstAggregate.
How to start a second transaction It is important to explicitly start a new transaction here. Most conventional systems handle a request in one transaction. Then you could use the default transaction propagation mode, which is REQUIRED. In our case we need REQUIRES_NEW to force a new transaction for updating SecondAggregate. This is the DomainEventProcessor updating SecondAggregate. In all fragments below, the code is kept simple and I left some code out to keep the focus on the event handling mechanism.
How to postpone event handling We could let the DomainEventProcessor handle the events right after the moment that FirstAggregate emits them. This would lead us into problems. The DomainEventProcessor would have Spring suspend the first transaction and then start a second one. The transactionmanager will commit the second transaction and then possibly rollback the first. One solution for this is to buffer the events. Then, after the commit, ‘flush’ the events to have them processed:
How to trigger logic after tx commit How to get Spring to flush a BufferedDomainEventSubscriber after the first transaction has successfully committed? It does not require much googling to find out that this is done using Spring's [TransactionSynchronizationManager] (http://docs.spring.io/spring/docs/3.2.4.RELEASE/javadocapi/ org/springframework/transaction/support/TransactionSynchronizatio nManager.html. This class enables you to register some transaction-related callbacks. Here is the class that implements the 'after transaction commit callback':
And here how to use TransactionSynchronizationManager to flush all buffered events to its subscribers. This, finally, is the application service receiving a command from a client:
So now what happens is that some client invokes FirstAggregateApplicationService.doActionOnFirstAggreate() and Spring starts transaction #1. FirstAggregate.changeState() gets invoked and will send out a domain event. TheBufferedDomainEventSubscriber accepts the domain event and stores it. If a rollback happens to transaction #1 then the event is ignored. If instead a commit takes place, Spring will invoke the afterCommit() which sends the events to the DomainEventProcessor.process(). Spring will wrap transaction #2 around this invocation.
What happens if we do not use REQUIRES_NEW Which of course is what I did in my first attempt. What I found what happens using default transaction propagation mode REQUIRES was that the changes to SecondAggregate were not persisted. So my first thought was: Is the logic executing outside a transaction? In other words, is Spring ignoring the second @transaction on DomainEventProcessor.process()? This seemed to be the case at first sight. There exist some conditions for Spring to recognize the @transactional. For example only public methods, proxied classes, ... But the class executing the second update is proxied, and the conditions seem to be met. So I needed to:
Investigate what is going on I have tried several ways of inspecting the transaction behaviour. For example logging the result of TransactionSynchronizationManager.isActualTransactionActive(). This may help to detect a situation where a transaction is never started, but it did not help me much with my problem. It told me that a transaction was always active whenever I updated my aggregates. So this could not explain why my changes were not persisted. What did help me was to log the transactional behaviour and then inspect the log files. I configured debug level logging and on org.springframework.orm.jpa on my own packages to see what is going on in JpaTransactionManager:
What I saw was:
I noticed two important things: 1. 2.
After the 'Start flush 1 event', Spring TM understands that we want to execute the process() logic inside a transaction. Spring recognizes the @transactional. But it seems to use the existing, already committed, transaction. It says: 'Participating in existing transaction', and no commit appears afterwards
Then I found an important note in the API doc of the TransactionSynchronization.afterCommit() that explains it all. The transaction is still active after the commit. Transactional code is executed, but never committed. The solution here is to explicitly use transaction propagation mode ROPAGATION_REQUIRES_NEW in the Processor.
Then the logs show:
Notice the Suspending current transaction, creating new transaction.... This line shows that the event processing code is actually running in a second transaction.
Summary 1. The need for REQUIRES_NEW transaction propagation is not very common in conventional architectures. When applying DDD tactical patterns, we can use it to separate transactions for multiple aggregate-updates. 2. The transactionmanager does not care if it executes transactional code in a transaction that it has already committed. It just runs the code while is does not actually participate in the transaction. 3. Configure your logging. The Spring logging is very good and it tells you exactly what is happening. Use it to learn. Resources and thanks to Vaughn Vernon - IDDD Andrei Zagorneanu - Transaction synchronization callbacks in Spring Framework, for pointing me to the TransactionSynchronization API docs. Tim Mattison @Transactional rules