Grails Integration Tests And Nested Transactions

In this post we’ll dig into some problems that can arise when using nested transactions in methods that are under test in integration test. I’ll also talk about when nested transactions might be needed.

Paint me a picture Ron

Dateline, early February 2016 - our hero is diligently trying to find a problem with the company’s content engine that’s causing reports to go missing and lengthy processes to die at the last minute.

The logging produced is scant, but indicates that the transaction for this work has been marked as ‘rollback only’, and once the outermost service method exits the transaction bails, undoing all of the database updates from the previous hour’s worth of work.

Digging in, no culprit can be easily identified, so it’s decided that the small units of work that make up the larger whole should themselves be wrapped in nested transactions - that way, no single piece of content can cause a rollback of the entire process.

The frustrating thing about this issue is that the output of the process, a set of files, was actually being produced without issue. Unfortunately, the link to where those items reside in our long term storage was supposed to be saved as part of the task representation - in our relational database - which meant the links where removed after the rollback! To the poor user it seemed the work would make forward progress, completion status updating all the way, but then at the very end bomb-out with a generic error message and no links to the expected files!

Hibernate exposes a means to use nested transactions. By doing this, any possible problem that occurs within the nested transaction will trigger a rollback of that inner transaction only! This is exactly the solution we needed, allowing for more a thorough investigation after unblocking our users.

NESTED vs REQUIRES_NEW

There’s a propagation behavior in Spring named REQUIRES_NEW - this behavior is different in a crucial way from NESTED. When running a nested transaction, it will not be commited until the outer transaction completes. A new (from REQUIRES_NEW) transaction will commit on completion and may not have the same visibility into pending transactions of the outer transaction as a nested one would.

This StackOverflow answer describes it as well.

Now, let’s look at exactly what is involved with getting some nested transactions into Grails.

Creating nested transactions in Grails

The first thing to do is mark out the space where the nested transaction should occupy. If it’s at all possible in your situation make the boundary at a service interaction layer. In Grails, the Spring Interceptor sits between services and provides transactional behavior based on annotations (or the application defaults if not specified through annotations). Let’s see what that might look like:

package so.dahlgren

import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional

class NestedTransactionService {

  @Transactional(propagation=Propagation.NESTED)
  void businessLogic() {
    // Do things that may end up throwing exceptions or causing other mischief
  }
}

The service above, NestedTransactionService, will run its businessLogic method inside a nested inner transaction of the caller’s transaction. The use of the Spring annotation @Transactional allows us to specify the propagation behavior, isolation level, and timeout. See the full documentation for more information.

A word of warning

As mentioned above, the Spring Interceptor sits between service. This means that intra-service method calls annotated with transaction metadata will be called without honoring the transaction configuration.

Here’s an example

package so.broken

import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional

// Don't do this!
class BrokenNestedTransactionService {

  // The method collaborators call
  void serviceEntryPoint() {
    // Intra-service method call - Spring Interceptor does not insert itself here!
    return businessLogic()
  }

  @Transactional(propagation=Propagation.NESTED)
  void businessLogic() {
    // Do things that may end up throwing exceptions or causing other mischief
  }
}

So what if we can’t insert a service boundary?

If you are refactoring existing code or for other reasons can’t break the dangerous logic out into a separate service, you may be wondering how to proceed. We can’t just extract it into a method, annotate it with @Transactional, and move on with life because of the Spring Interceptor behavior. Luckily, Spring offers a means to do this with code - a means that also includes greater flexibility.

Introducing the TransactionTemplate

Spring’s TransactionTemplate provides ‘programmatic transaction demarcation’. It provides an execute method taking a TransactionCallback (called synchronously). The signature for this method is:

Object execute(TransactionCallback action)

The returned Object will be whatever is returned from action - null if action doesn’t return anything. What makes the TransactionTemplate of interested, beyond this execute method, is the configurability it provides. A new TransactionTemplate can be constructed with a TransactionDefinition which collects all of the items provided by the @Transactional annotation (timeout, propagation behavior, isolation level, etc) - or - configured post-construction using bean style mutators (TransactionTemplate actually implements the TransactionDefinition interface). This will prove crucial when we get to the test interaction piece.

Let’s look at a TransactionTemplate example:

package so.dahlgren

import org.springframework.transaction.TransactionStatus
import org.springframework.transaction.support.TransactionCallback
import org.springframework.transaction.support.TransactionTemplate

class ServiceWithNestedTransaction {
  // Injected via the resources.groovy definition (detailed later)
  TransactionTemplate txTemplate

  // Callers interact with the service through this entry point
  BusinessObject serviceEntryPoint() {
    BusinessObject result
    // pre-amble stuff
    // ...
    result = (BusinessObject) txTemplate.execute(
      new TransactionCallback() {
        @Override
        Object doInTransaction(TransactionStatus status) {
          return businessLogic(input)
        }
    })
    // more stuff
    // ...
    return result
  }

  // May throw exceptions, fail validations, or otherwise break a transaction
  BusinessObject businessLogic(def inputs) {
    // stuff
    // ...
  }
}

Now we have wrapped the call to businessLogic in a transaction boundary. How do we know this is using a nested transaction and not a new or existing one? By configuring the txTemplate in the Grails bean definition file - resources.groovy.

Let’s take a look:

import org.springframework.transaction.support.TransactionTemplate

beans = {
  // other beans defined
  // ...
  txTemplate(TransactionTemplate) { bean ->
      isolationLevel = TransactionTemplate.ISOLATION_DEFAULT
      propagationBehavior = TransactionTemplate.PROPAGATION_NESTED // <-- Nesting!
      timeout = 500
      transactionManager = ref('transactionManager') // <-- provided by Grails
  }

One final but important note

By default the transactionManager bean provided by Grails does not support nested transactions. You’ll know that you missed this step when you see error messages about ‘nested transactions are not supported’. Luckily this is an easy step - in your BootStrap.groovy (or similar application startup logic) just add the following:

class BootStrap {
  def transactionManager

  def init = { ServletContext servletContext ->
    // Allow nested transactions to exist
    transactionManager.nestedTransactionAllowed = true
  }
}

See the full documentation for more info.

Ron, this is great! … WAIT MY INTEGRATION TESTS ARE BREAKING!!!

Fear not readers - a solution exists!

The problem

Integration test cases in Grails run within a helpfully provided transaction. This transaction ensures that database changes in one test case don’t leak into subsequent test cases. For some reason (hit me up on Twitter and tell me why) use of nested transactions in this manner causes the outer transactions to be commited and leak over into integration tests. In our codebase, this manifests as uniqueness constraint violations when creating test fixtures.

The solution

Using the support for Environment specific configuration in Grails, we will use a different TransactionTemplate in our service. Here’s an updated resources.groovy showing what I mean:

import grails.util.Environment
import org.springframework.transaction.support.TransactionTemplate

beans = {
  // other beans defined
  // ...
  txTemplate(TransactionTemplate) { bean ->
      isolationLevel = TransactionTemplate.ISOLATION_DEFAULT
      propagationBehavior = TransactionTemplate.PROPAGATION_NESTED // <-- Nesting!
      timeout = 500
      transactionManager = ref('transactionManager')
  } // default binding, but for tests we use...
  Environment.executeForCurrentEnvironment {
    test {
      txTemplate(TransactionTemplate) { bean ->
        isolationLevel = TransactionTemplate.ISOLATION_DEFAULT
        propagationBehavior = TransactionTemplate.PROPAGATION_REQUIRED // <-- _NOT_ nested
        timeout = 10
        transactionManager = ref('transactionManager')
      }
  }
}

With the call to Environment.executeForCurrentEnvironment we inform grails that for the test environment, a non-nesting TransactionTemplate should be used.

MISSION ACCOMPLISHED.

Until next time friends!