Music, software, life… and stuff.
[ Twitter ] [ GitHub ] [ Linked In ]
Update: I have released a plugin that makes working with scoped proxies a little bit easier.
Grails has supported “scoping” services for as long as I have been working with it. Surprisingly, you very rarely here of people using this feature. For my money, it’s one of the lowest cost techniques available to avoid the inherent spaghetti in referencing the session and request (for all intents and purposes) global objects.
This feature is built on top of Spring’s bean scoping. However, you don’t need to know much Spring to get value out of this in Grails.
Scoping a service is as simple as declaring a static scope property with the name of the desired scope…
class MyScopedService {
static scope = 'session'
}
So now, for each distinct session there will be a distinct instance of MyScopedService in our application.
An easy to understand use case for this might be a shopping cart…
class CartService {
static scope = 'session'
void addCartItem(cartItem) {
// …
}
def getPrice() {
// …
}
}
We would use this in our controller like so…
class CartController {
def cartService
def addItem = {
cartService.addItem(/* cart item */)
}
def price = {
println "Cart price is: " + cartService.price
}
}
You might be wondering just which session that cartService belongs to. Grails controllers are prototype scoped beans (which means a new one is created whenever it is retrieved from the application context) and only ever created inside of a request environment. Therefore, a Grails controller always lives within the context of a session. So when the controller instance is created by Grails at the start of the request, it can be injected with the instance of cartService that correlates to the session that the request is for.
But what about objects that don’t live inside a request (like taglibs)? For that we need a scoped proxy.
So we want to have a taglib that renders the current price of the shopping cart. We could pass the cartService instance to the view layer via our controller action and then pass it to the taglib, but that gets tiresome real quick when you start doing it for lots of actions. Since our cart is now a Spring managed bean we could try adding it as a dependency of our taglib…
class CartTagLib {
def cartService
def price = {
out << cartService.price
}
}
If you try and do this you are going to get…
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cartService': Scope 'session' is not active for the current thread;
Grails taglibs are singleton scoped so are created at application startup when the notion of session doesn’t make any sense. What we can do though is create a singleton proxy that delegates to the relevant cartService object at execution time (taglibs are always executed during a request so there will be session when the tag is executed).
With Grails 1.2 and earlier, we have to create this proxy manually by adding this to grails-app/conf/resources.groovy…
import org.springframework.aop.scope.ScopedProxyFactoryBean
beans = {
cartServiceProxy(ScopedProxyFactoryBean) {
targetBeanName = 'cartService'
proxyTargetClass = true
}
}
This, in effect, creates a singleton bean, which is a dynamically generated subclass of CartService, that delegates method/property calls to the cartService instance stored in the session. Check out Spring’s docs on scope proxies if you want more information on this.
Now we just rewrite our taglib to use our proxy…
class CartTagLib {
def cartServiceProxy
def price = {
out << cartServiceProxy.price
}
}
You can now use your cartServiceProxy anywhere like other services or filters. As long as the code that calls cartServiceProxy is executed in a request environment everything will work as expected in a completely thread safe manner.
Grails 1.3 will make scoped beans even more attractive by removing the need to define your own proxies. All you will have to do is…
class CartService {
static scope = 'session'
static proxy = true
}
To have a proxy named cartServiceProxy created for you. You can track this new feature via the Grails issue tracker.
Grails 1.3 will also support scoped tag libs in the same manner as services.
When integration testing scoped services, you need to be aware of the request/session lifecycle semantics.
Each test method is run in it’s own unique environment. This means two things; you must use a proxy to access a request or session scoped in a test, and each test method will have it’s own distinct underlying service instance.
class SomeTests extends GroovyTestCase {
import grails.plugin.spock.*
class ScopedServiceSpec extends IntegrationSpec {
def sessionScopedServiceProxy
def requestScopedServiceProxy
void test1() {
expect:
sessionScopedServiceProxy.property == null
requestScopedServiceProxy.property == null
when:
sessionScopedServiceProxy.property = 1
requestScopedServiceProxy.property = 1
then:
sessionScopedServiceProxy.property == 1
requestScopedServiceProxy.property == 1
}
void test2() {
expect:
sessionScopedServiceProxy.property == null
requestScopedServiceProxy.property == null
when:
sessionScopedServiceProxy.property = 2
requestScopedServiceProxy.property = 2
then:
sessionScopedServiceProxy.property == 2
requestScopedServiceProxy.property == 2
}
}
For me it’s really about expressing intent. Some aspects of your application, like a shopping cart, are so pervasive that they end up everywhere in your code. It’s very hard to track down who is getting something from or setting something in the session or request objects in controllers, taglibs or filters. If you use scoped services, it’s very easy to see who is doing what. I can tell a controller does something with the cart because I see def cartService at the start of the class. That’s easier to spot than session.cart buried in an action. I also find it allows me to stay more focussed in the language of my problem domain.
Scoped services are a great tool to have in your arsenal. In an upcoming post, I’ll be discussing using custom scopes or lifecycles for your scoped services.
Posted: Mar 9th, 2010 @ 9:46 pm