During the recent "Rails/Grails kerfluffle":http://blog.thinkrelevance.com/2008/1/11/how-to-pick-a-platform, one of the memes that came up over and over again was that Grails had a specific feature which Rails lacked and that it was a Big Deal(tm). Specifically, Grails defines a service layer with automatic transaction demarcation which allows you to remove complex domain manipulation code from your controllers, leaving them to deal with loading resources and redirecting to views.
As a thought experiment, we set out to find out what would need to happen to enable this in a Rails app. By the end, we'd written the plugin we are releasing today: SimpleServices. It amounts to about 12 lines of code, but that isn't really a boast, as we'll see in a minute.
Before we look at SimpleServices, we should discuss what makes up the service layer in Grails:
When we wrote this plugin, we were attempting to get out the most robust implementation of the default use case, which is:
When you install the plugin, you can create a folder in RAILS_ROOT/app called "services". Within this folder, you can define one or more service classes. A service class has a name that ends with "Service" and derives from Service::Base
. The plugin causes these to be auto-loaded in the traditional Rails fashion. Each method in the service is automatically wrapped in a transaction, and any errors raised within the method will cause the transaction to rollback.
Within your controllers, you can declare which services you will be using in your actions. For each service you declare your need for, a method will be added to the controller in the form of "#{service_name}_service" which hides all the service instantiation code.
You aren't forced to use services from controllers, either. If you want the declarative support, but in your models, you just include SimpleServices::ServiceInjection
in whatever class you want. For example, you could include it in ActiveRecord::Base
and have it available for all your models. Likewise, you can create instances of services directly, without declarative support, by calling AccountService.instance
.
h2. Known Issues
We'd love to hear if people in the Rails community find this useful. We are playing around with it in a couple of apps, and it does have the benefit of cleaning up the controller codebase and providing zero-thought transaction support. Yay, services! But, we'd love to hear from others on this, and on how it could be improved.
Good luck, and happy servicing.