Sunday, April 06, 2008

Pluggable Service Implementations in Grails

This weekend I was working on a StorageService service in Grails. The service takes a file, stores it somewhere, and then returns the URL where the file can be retrieved from. I can envision having multiple implementations of this service: one that simply puts the file in a directory for something like Tomcat/Apache/Nginx to serve up, one that puts the contents of the file into a database blob and has a controller that retrieves it from the database and serves it up, and one that stores the file on Amazon S3.

I wanted to be able to easily specify which implementation to use without having to refactor my code and I wanted to be able to configure the implementation based upon the environment. For development and integration testing, it would be easiest to test with a simple File-based service. When I actually rolled it out to production, I would likely want to use something more scalable like Amazon's S3.

I didn't know how (if?) Grails handled this situation, so I decided to write my own. I drew inspiration from Shawn Harstock's post about Fun with Grails Configuration Files for managing service configuration in the various environments.

The first thing I did was create a stub service, StorageService:

import org.codehaus.groovy.grails.commons.ApplicationHolder

class StorageService {
boolean transactional = true
boolean initialized = false
def provider = null

String store(File file) {
// initialize our provider
if (!initialized) {
initializeProvider()
}

// use our provider or log the error
if (provider == null) {
// log the error
return null
} else {
return provider.store(file)
}
}

def initializeProvider() {
initialized = true;

// get our provider name
def prefix = ApplicationHolder.application.config?.storage?.provider
if (prefix == null) {
prefix = "Default"
} else {
def index = prefix.lastIndexOf('.')
if (index == -1) {
prefix = prefix[0].toUpperCase() + prefix[1..-1]
} else {
prefix = prefix[0..index] + prefix[index+1].toUpperCase() + prefix[(index+2)..-1]
}
}

// try creating the provider
provider = Class.forName("${prefix}StorageProvider")?.newInstance()
}
}

The service looks up and loads the storage provider class based on a configuration parameter. For example, my Config.groovy file might look like this:

...
environments {
development {
storage {
provider="file"
}
}
test {
storage {
provider="org.andrill.mock"
}
}
production {
storage {
provider="s3"
}
}

}
...


In development, the StorageService would try to load the class FileStorageProvider and delegate calls to it. In testing, it would try to load the class org.andrill.MockStorageProvider and delegate calls to it. And finally, in production it would try to use the S3StorageProvider.

My controllers don't need to know which provider implementation, they just declare a
StorageService storageService field, which Spring auto-wires for them, and go. It works like a charm.

So my only question is, did I just re-invent the wheel? Does Grails already have a mechanism for handling this? If not, how would you accomplish the task?

Update: Check out David Hernández's explanation of how to do this with Spring. No icky, ClassForName needed. Nice work, David!

Cheers.

6 comments:

dahernan said...

Great!!

I don't know if exist a standard way. But I'd try to use IoC and Spring instead ClassForName.

In Grails documentation (Using the Srping DSL)

http://grails.org/doc/1.0.x/guide/14.%20Grails%20and%20Spring.html#14.3%20Runtime%20Spring%20with%20the%20Beans%20DSL

you can use resource.groovy to define beans that depends environment, hence you can inject generic provider bean and define your three beans per environment.

Maybe more elegant.

Cheers

Josh Reed said...

The ClassForName bit does feel a bit dirty, doesn't it? And in actuality if you anticipated bad Config values getting out, you'd probably want to wrap the call in some exception handling.

Thanks for the link, I'll it check out. I'm guessing the Spring route would just require I create and implement an interface (which would be good in this case so I could implement providers on the Java side as well). Then I could inject an implementation under that interface per environment.

Cheers,
Josh

Anonymous said...

Any thoughts on how to handle files already on the file system?

Josh Reed said...

Hi Anonymous,

Is your question about serving existing files for download from a Grails app? Or is it how I would handle existing files in a storage provider implementation?

Cheers,
Josh

dahernan said...

Hi Josh,

I've posted the Spring DSL way.

http://dahernan.net/2008/04/grails-plugglabe-service-reloaded.html

Cheers

Josh Reed said...

Nice work, David! I added a pointer to your blog entry.

Cheers,
Josh