Fork me on GitHub

LD.

Music, software, life… and stuff.

[ Twitter ] [ GitHub ] [ LinkedIn ]

Putting the Artefact API to work in your Grails app

I haven’t seen many cases of people putting the Grails’ artefact API to use in their application. It’s likely that most Grails’ users aren’t aware that it exists, which in many ways is a good thing… it is after all an implementation concern.

You can however put it to work in your own application.

What is it?

Perhaps it’s best to discuss what an artefact is in Grails first. An example of an artefact is a service class. Think of what happens when your grails application starts; it finds all the service classes, creates an instance of each (typically) and autowires the dependencies. Grails defines a class whose instances represent a single service class and an associated handler. This is what I am collectively calling the Artefact API.

Why use it?

The biggest reason these days is by far the hot reloading support. That is, if you change an artefact class while the application is running, Grails (with some help from you) will reload the class and you get the changes without a restart. Of course, we are all familiar with this.

There is a more subtle benefit though. You may have some kind of prominent & pervasive concept in your application (like a service is a concept). You might find that creating an artefact for this concept can make your life easier.

An example use case

Consider some kind of eStore, with a wide range of products. Our products have simple persistence requirements but can have wildly varying behaviours in the system, which are likely to get more complex over time. I try to avoid extensive subclassing of domain objects when the classes primarily vary in behaviour and not data. So we need to find another way to implement the varying behaviour.

The ProductHandler

Each product is going to provide a handler (terrible name I know) that is responsible for the majority of the product’s behaviour. Here is our product domain class…

class Product {
    String name
    
    // don't want to try and persist the handler
    static transients = ['handler'] 
    
    ProductHandler getHandler() {
        // somehow get the handler for this product
    }
}

And we have a ProductHandler interface for good measure…

interface ProductHandler {
    void setProduct(Product)
    boolean isCanBeSold()
}}

A non artefact solution

One kind of traditional approach would be to have each product persist a key that refers to a particular product handler, and to have each product handler class register itself with some kind of registry.

class Product {
    String name
    String handlerKey
    
    def productHandlerService // autowired 
    
    // don't want to try and persist the handler
    static transients = ['handler']
    
    protected handler
    ProductHandler getHandler() {
        if (!handler) {
            handler = productHandlerService[this]
        }
        handler
    }
}

// Our registry
import grails.spring.BeanBuilder
class ProductHandlerService {
    def grailsApplication // autowired
    static private registry = [:].asSynchronized()
    
    static register(String key, Class handlerClass) {
        registry[key] = handlerClass // should check that handlerClass implements ProductHandler
    }
    
    protected createBeanBuilder() {
        new BeanBuilder(grailsApplication.mainContext, grailsApplication.classLoader)
    }
    
    protected instantiateHandler(handlerClass, Product product) {
        createBeanBuilder().with {
            beans {
                handler(handlerClass, product: product) {
                    // autowire the handler so it can use services etc.
                    it.autowire = true
                }
            }
            createApplicationContext().getBean('handler')
        }
    }
    
    def getAt(Product product) {
        def handlerClass = registry[product.handlerKey] // assume there is always a handler
        instantiateHandler(handlerClass, product)
    }
}

// Example Handler (in src/groovy dir)
class OnlyOnTuesdayProductHandler implements ProductHandler {
    static {
        ProductHandlerService.register('onlyOnTuesday', OnlyOnTuesdayProductHandler)
    }
    Product product
    boolean isCanBeSold() {
        new GregorianCalendar().get(Calendar.DAY_OF_WEEK) == Calendar.TUESDAY
    }
}

This will get the job done (albeit in a cumbersome way), but it suffers from ProductHandler implementations not being hot-reloadable.

Creating a ProductHandler artefact

We can make things a bit easier and support hot reloading by creating an artefact for product handlers.

To do this we need a plugin…

class ProductHandlerGrailsPlugin {
    def version = "0.1"
    def grailsVersion = "1.2.* > *"
    
    // register the artefact handler
    def artefacts = [ProductHandlerArtefactHandler]
    
    // watch for any changes in these directories
    def watchedResources = [
        "file:./grails-app/productHandlers/**/*ProductHandler.groovy",
        "file:../../plugins/*/productHandlers/**/*ProductHandler.groovy"
    ]
    
    // Grails calls this when one of the files matching 'watchedResources' changes.
    // We have to actually swap in the new class when the source changes
    // There isn't anything special here, it's just boilerplate.
    def onChange = { event ->
        if (application.isArtefactOfType(ProductHandlerArtefactHandler.TYPE, event.source)) {
            def oldClass = application.getProductHandlerClass(event.source.name)
            application.addArtefact(ProductHandlerArtefactHandler.TYPE, event.source)

            // Reload subclasses
            application.productHandlerClasses.each {
                if (it.clazz != event.source && oldClass.clazz.isAssignableFrom(it.clazz)) {
                    def newClass = application.classLoader.reloadClass(it.clazz.name)
                    application.addArtefact(ProductHandlerArtefactHandler.TYPE, newClass)
                }
            }
        }
    }
}

We also need a artefact handler implementation (which has to be in Java due to legacy reasons)…

import org.codehaus.groovy.grails.commons.ArtefactHandlerAdapter;

public class ProductHandlerArtefactHandler extends ArtefactHandlerAdapter {

    // the name for these artefacts in the application
    static public final String TYPE = "ProductHandler"; 

    // the suffix of all product handler classes (i.e. how they are identified as product handlers)
    static public final String SUFFIX = "ProductHandler"; 
    
    public ProductHandlerArtefactHandler() {
        super(TYPE, ProductHandlerClass.class, DefaultProductHandlerClass.class, SUFFIX);
    }
}

And a ProductHandlerClass interface (again in Java)…

import org.codehaus.groovy.grails.commons.GrailsClass;

public interface ProductHandlerClass extends GrailsClass {}

And a DefaultProductHandlerClass implementation of that interface (again in Java)…

import org.codehaus.groovy.grails.commons.AbstractGrailsClass;
import org.codehaus.groovy.grails.commons.GrailsClassUtils;
 
public class DefaultProductHandlerClass extends AbstractGrailsClass implements ProductHandlerClass {

    public DefaultProductHandlerClass(Class clazz) {
        super(clazz, ProductHandlerArtefactHandler.SUFFIX);
    }
    
    // This method will get the static property 'key' on the underlying 
    // ProductHandler class that it represents.
    public String getKey() { 
        Object key = GrailsClassUtils.getStaticPropertyValue(getClazz(), "key");
        if (key == null) {
            return null;
        } else {
            return key.toString();
        }
    }
}

That’s a shotgun intro to the Grails Artefact API classes like GrailsClass and ArtefactHandlerAdapter just covering the stuff we need. Check out the linked to source for deeper detail.

Here is what we have done…

  1. Create a plugin that registers an artefact handler for product handlers
  2. Register for changes to source in grails-app/productHandlers in the app and plugins
  3. Provide an onChange handler that reloads product handler classes when they change
  4. Provide an ArtefactHandler implementation that specifies what a product handler class is
  5. Provide a GrailsClass subinterface for product handler classes
  6. Provide an implementation of that interface that returns the static property key on the underlying class (among other things)

Putting it together

Armed with our new artefact, we can now simplify our initial implementation…

// Our registry
import grails.spring.BeanBuilder
class ProductHandlerService {
    def grailsApplication // autowired
    
    protected createBeanBuilder() {
        new BeanBuilder(grailsApplication.mainContext, grailsApplication.classLoader)
    }
    
    protected instantiateHandler(handlerClass, Product product) {
        createBeanBuilder().with {
            beans {
                handler(handlerClass, product: product) {
                    // autowire the handler so it can use services etc.
                    it.autowire = true
                }
            }
            createApplicationContext().getBean('handler')
        }
    }
    
    def getAt(Product product) {
        def productHandlerClasses = grailsApplication.productHandlerClasses // a list of DefaultProductHandlerClass
        def handlerClass = productHandlerClasses.find { it.key == product.handlerKey } // find the one with the matching key
        instantiateHandler(handlerClass, product)
    }
}

// Example Handler (in grails-app/productHandlers dir)
class OnlyOnTuesdayProductHandler implements ProductHandler {
    static key = 'onlyOnTuesday'
    Product product
    boolean isCanBeSold() {
        new GregorianCalendar().get(Calendar.DAY_OF_WEEK) == Calendar.TUESDAY
    }
}

Being more conventional

Each of our product handler classes must specify a key, that products use to link themselves to a particular handler implementation. You could do away with this, and infer the key from the class name quite easily.

class ProductHandlerService {
    // …snip…
    
    def getAt(Product product) {
        def handlerClass = grailsApplication.getProductHandlerClass(product.handlerKey)
        instantiateHandler(handlerClass, product)
    }
}

The grailsApplication.getProductHandlerClass() method given the value "onlyOnTuesday" would return the OnlyOnTuesdayProductHandler class object.

In this particular case I personally wouldn’t do this as every time I renamed a product handler class I would have to update the products that use that handler in the DB, but other situations may not have this issue.

Summary

All product handler classes are now reloadable, which would obviously be a substantial productivity boost, without all that much work on our part. This approach could be used for any kind of concept or set of classes in your application that don’t fit into the standard controller, service, taglib etc. pattern.

The Artefact API can seem a bit confusing if you don’t take into consideration that this was the heart of Grails in the early days. It’s still there and is used by a great many plugins (e.g. grails-quartz) as well as being used extensively internally in Grails, but it doesn’t quite have the same role that it used to.

Hopefully this has shown you how you can, and why you might want to, use the artefact API internally in your applications.

Posted: Jun 1st, 2010 @ 10:42 pm

Tags: #software  #grails  

Comments

Archive · RSS · Theme by Autumn