Thursday, 11 December 2008

Caching Grails Services

I've been plugging away for a few days on adding caching to Grails services. At first I tried to go down the road of decorating the services' metaClasses but since Grails uses an AOP proxy to add transactional behaviour to services this wouldn't work - the metaClass I got from the injected service object was not the same one my plugin had added stuff to. Also annotations on the service class were unavailable from the proxy.

The Spring Modules project has a nice caching implementation that uses annotations to decorate methods with caching and flushing behaviour. Getting this to work with Grails, was reasonably straightforward. As it turns out one additional config line made the difference between the spring-modules example config and something that worked in Grails.

I based the config on the annotation example from the Spring Modules documentation, converting the Spring XML to Grails BeanBuilder format:

    import org.springframework.aop.framework.autoproxy.*
import org.springmodules.cache.annotations.*
import org.springmodules.cache.interceptor.caching.*
import org.springmodules.cache.interceptor.flush.*

def doWithSpring = {

// declaration of cacheManager and cacheProviderFacade omitted - implementation specific

autoproxy(DefaultAdvisorAutoProxyCreator) {
proxyTargetClass = true
}

cachingAttributeSource(AnnotationCachingAttributeSource)

cachingInterceptor(MetadataCachingInterceptor) {
cacheProviderFacade = ref("cacheProviderFacade")
cachingAttributeSource = ref("cachingAttributeSource")
def props = new Properties()
props.myCachingModel = 'cacheName=MY_CACHE_NAME'
cachingModels = props
}

cachingAttributeSourceAdvisor(CachingAttributeSourceAdvisor, ref("cachingInterceptor"))

flushingAttributeSource(AnnotationFlushingAttributeSource)

flushingInterceptor(MetadataFlushingInterceptor) {
cacheProviderFacade = ref("cacheProviderFacade")
flushingAttributeSource = ref("flushingAttributeSource")
def props = new Properties()
props.myFlushingModel = 'cacheNames=MY_CACHE_NAME'
flushingModels = props
}

flushingAttributeSourceAdvisor(FlushingAttributeSourceAdvisor, ref("flushingInterceptor"))
}
The vital bit is the proxyTargetClass = true on the autoproxy bean. I won't pretend to understand what that's doing - I just noticed that's how ServicesGrailsPlugin was getting the transactional proxies to work.

After that it's a simple case of annotating service methods (in fact methods on any Spring-managed bean but services are the obvious use case).

    import org.springmodules.cache.annotations.*

@Cacheable(modelId = "myCachingModel")
def getSomethingInAnExpensiveWay(param1, param2) {
// ...
}

@CacheFlush(modelId = "myFlushingModel")
def updateSomething(param1, param2) {
// ...
}
This works fine on transactional and non-transactional Grails services. You can see the cache operations by setting log4j.logger.org.springmodules.cache = "trace".

The actual caching implementation used can be anything - spring-modules supports ehcache, oscache, JBoss cache, JCS, etc. All that's required is to wire in the cacheManager and cacheProviderFacade beans as described in the Spring Modules documentation.

I've bundled the results up as a Grails plugin (grails install-plugin springcache). It uses a ConcurrentHashMap backed simple cache implementation by default but wiring in ehcache or oscache is easy.

Caching and flushing models are configured in Config.groovy, e.g.:

    springcache {
cachingModels {
cachingModel1 = 'cacheName=CACHE_1'
cachingModel2 = 'cacheName=CACHE_2'
}
flushingModels {
flushingModel1 = 'cacheNames=CACHE_1,CACHE_2'
}
}
Particular caching implementations may have further options or require additional external config (such as ehcache's ehcache.xml file).

To disable the plugin you can set springcache.disabled=true. For example, it may be desirable to disable the plugin in the test environment.

Edit: Documentation added on grails.org

3 comments:

Agile Enforcer said...

Nice!

Unknown said...

The reason you have to say proxy target class is because the service is not implementing an interface - so Spring has to be told.

I remember the good ol' days when Services implemented interfaces. ;-)

Rob said...

Yeah, because I love writing out all my method declarations twice.