Put Your Controllers on a Diet, Use a Service Layer
I recently hired my first employee, Jeff. If you’ve been reading my blog you may have noticed a few recent entries by Jeff. The nice thing about an employee is that you can, within the boundaries of the employees willingness to deal with it, give them whatever work you don’t want to do. Or, as in my case, don’t have the time to do.
This has, unfortunately, put Jeff in a tough spot. See, I have him working on a couple projects right now. One of these projects is an “old” Model-Glue application which I myself wrote most of a couple years ago. The application in question is a Model-Glue 1.1 application and also uses Reactor and ColdSpring.
Now, by using Model-Glue, Reactor and ColdSpring you would think that this would be an extremely maintainable application. Well this is true and not true. It’s proven quite easy to maintain the visual side of the application, but it’s turned out to be a pain to update the logical side of the application.
See, when I first started the project I didn’t have the faintest clue what a services layer was. I mean, I had a pretty good idea what a model was and I knew how to write CFCs. I’d written Reactor by then so I wasn’t completely clueless (depending on your personal perspective).
Actually, it’s important to note that I didn’t start out using ColdSpring in the application Jeff is supporting. Instead, I used ChiliBeans, which is a simple component used to configure CFCs with XML. Think of it as a very, very, very light inversion of control (IOC) component.
This was quite useful for configuring CFCs. I wrote all sorts of config components. An EmailConfig would have properties to define who sent an email and who gets BCCed when an email is sent and more. A PaymentProcessorConfig might define all the values needed to process a transaction. I think you get the point. So, yes, ChiliBeans was great for configuring some of these CFCs, but that’s all I used it for.
Somewhere along the lines I started to use ColdSpring because of Autowiring. Autowiring is a process by which, when Model-Glue starts up, it will match beans configured in ColdSpring to setters on your application’s controllers. If matches are found, the beans will automatically be set into your controller for you. This saved me a few lines of code and I quickly adopted ColdSpring.
At first I used ColdSpring as a substitute for ChiliBeans. In fact, a lot of my projects from this time period still have a Beans.xml file which was simply a merging of all my individual bean xml files from ChiliBeans. Sadly, this is what Jeff is still stuck with.
So, if I’m only using ColdSpring for simple configuration, then how do I make use of these configuration settings?
I picked up the term Fat Controllers from Jared Rypka-Hauer. A Fat Controller is a controller in an MVC application where there is too much logic (or any, for that matter) in the controller.
Let’s say, for the sake of argument, that you’re sending an email from a Model-Glue application. You’ll probably have a form that submits to an event handler. The event handler would fire a message that’s mapped to an event handler on your controller. Here’s what a hypothetical Fat Contoller method might look like:
<cffunction access="Public" hint="I send an email." name="DoSendEmail" output="false" returntype="void"> <cfargument name="event" required="true" type="ModelGlue.Core.Event"> <cfset EmailConfig=getEmailConfig() var/> <cfmail from="#EmailConfig.getTo()#" subject="#EmailConfig.getSubject()#" to="#arguments.event.getValue("from")#"> Dear #arguments.event.getValue("name")#, We thought you might want to buy our penny stocks. Etc. etc. etc. Thanks, Some Company </cfmail> </cffunction>
The getEmailConfig() method above would be set via Autowiring to a CFC configured via ColdSpring. The EmailConfig and additional values from the event are used to send an email via the cfmail tag.
But the thing is, what the heck does this buy us? About the only thing I can think of is separation of configuration from my application. However, I’m still applying procedural techniques within an OO framework. What could be more procedural than getting values and then doing stuff with them in a long line of code?
The example above is fairly simple for the purposes of this article, but from the application that Jeff’s supporting there are controller methods with 50 or more lines of code in them.
So now, a couple years after the fact, Jeff is thrown feet first into this application and asked to make some nontrivial changes. Let’s pretend that he’s working with that email controller above. Perhaps the client has decided that they want to use a Flex or Ajax form to sends the email message without reloading the page the user is on. How do you reuse the code in that controller? Well, long story short, you either put together a hack or you don’t reuse it at all.
The Service Layer
Let’s say that, instead of the controller above, we had CFC that sent an email. For the sake of argument the CFC has a method on it with this signature:
Without getting into the implementation, I think it’d be safe to say that this function performs the service of sending an email message. So let’s say that this is a “Service” CFC.
A Service Layer is made up of a collection of Service CFCs. Each service CFC has a collection methods with simple signatures that do things. They services create a faade for your application’s complex business logic. In many cases each use case in your application is matched by one method in a service.
Let’s say you have a content management system where users can add and edit content, delete content, publish content and view content. You might end up with a ContentService CFC with methods similar to this: saveContent(), deleteContent(), publishContent(), and getContent().
The nice thing about these CFCs is that they’re easily exposed to an HTML-based framework like Model-Glue, to Flex or Flash via remoting or to other languages and platforms via web services.
Getting back to our email use case, our controller could easily look more like this:
<cffunction access="Public" hint="I send an email." name="DoSendEmail" output="false" returntype="void"> <cfargument name="event" required="true" type="ModelGlue.Core.Event"> <cfset getEmailService().sendEmail(arguments.event.getValue("from"), arguments.event.getValue("name"))/> </cffunction>
Let’s ignore the implementation of the sendEmail method. It doesn’t matter. All we need to know is that if we call this method an email will be sent. But, what about all the other data that comes out of the email configuration cfc we were using before? We could easily use ColdSpring to configure the EmailService. Here’s an example configuration:
<bean class="model.email.EmailService" id="EmailService"> <constructor-arg name="from"> <value>firstname.lastname@example.org</value> </constructor-arg> <constructor-arg name="subject"> <value>Buy our stocks</value> </constructor-arg> <constructor-arg name="contentFile"> <value>buyStocks.txt</value> </constructor-arg> </bean>
Because the service is configured via ColdSpring it can be autowired into your controller. Heck, if you designed an API for your email service that was flexible enough, you could wire it into other controllers that might have a need to send emails from time to time.
This makes it very easier to reuse your application’s logic. It makes it more maintainable. It applies time tested design patterns. It slices, it dices, it even make julian fries.
So, the moral of the story is to put your controllers on a diet, use a Service Layer.