Objects and Frameworks - The Service Layer

Posted on Sep 17, 2006

During many of the mailing list discussions that I follow regarding object-oriented development in ColdFusion, we often get someone asking for help organizing their code properly. Inevitably someone mentions how you should keep business logic in the service layer. This will often elicit a response from the questioner that they understand beans, DAOs and gateways but don't know what the service layer is (or something along these lines). So what I would like to achieve here is a brief discussion of what the service layer is for beginners (therefore I am going to emphasize simplicity possibly over technical correctness at times).I have discussed in past posts on this topic (i.e. OO and frameworks) that often things begin to make more sense once you understand the need for them. For instance, ColdSpring makes much more sense once you see the difficulty of managing dependencies within your application. However, if you are new to OO, ColdSpring may seem to add another layer of complexity without much payoff. Well, this is how I came around to understanding the service layer, I saw the problem first.

I was building my first significant Model-Glue application following an example that someone had posted online. While it was a great example for beginners of building a small application, it had all of the application's business logic in the controller/listener. As my application progressed, I started thinking how this "über-controller" was making my application more maintainable. Yes, I could make multiple controllers, but even then they would each be huge and didn't seem very easy to maintain. Also, what if I moved from Model-Glue to another framework? The controller CFC has Model-Glue specific code and would mean that everything would have to be heavily modified or rewritten. Plus, other than copy/paste, I couldn't really reuse much of this code. This just didn't seem like the right way to do things.

Well, after some direction from Peter Farrell, I realized it was not. First of all, my controller/listener CFC should be as dumb as possible - meaning it should ideally contain no business logic whatsoever. It should say, you want to get users, I have a users service and I will call the getUsers() method and return the results. Here is an example function from my Mach-ii listener (i.e. controller) from my CFC generator application that gets a list of available datasources (on a side note, my generator actually does generate a *very* generic service component for you to modify):

<cffunction name="getDSNs" access="public" returntype="struct" output="false">    <cfargument name="event" type="MachII.framework.Event" required="yes" />    <cfreturn variables.adminAPIService.getdatasources() /> </cffunction>

All this does is call the appropriate function from the appropriate service. It is interesting to note, this would look essentially the same for Model-Glue. The service layer would now contain any business logic required to complete this task, calling any beans, DAOs and Gateways as needed.

If we are talking about users, for example, you can think of the service as the users API. So, for users, you can even begin to think of what you would need from an users API...I need to get a user, get a list of users, save a user, delete a user and perhaps validate a user. So, what would your service look like? Let's borrow an example from Kurt Wiersma's excellent AppBooster mach-ii starter application, it has getUser(), getUsers(), saveUser() and authenticateAdminUser() like we discussed might be needed for the API. Now let's look at the method that handles getting a user:

<cffunction name="getUser" access="public" returntype="appbooster.model.user.user" output="false">    <cfargument name="user_id" type="numeric" required="yes">    <cfset var user = "">    <cfset var roles = 0>    <cfset var i = 0>    <cfif arguments.user_id neq 0>       <cfset user = variables.userDAO.read(arguments.user_id)>       <cfset roles = user.getRoles()>       <cfif user.getAddress().getID() neq 0>          <cfset user.setAddress(getAddress(user.getAddress().getID()))>          <cfloop from="1" to="#arrayLen(roles)#" index="i">             <cfset roles[i] = variables.roleService.getRole(roles[i].getID())>          </cfloop>          <cfset user.setRoles(roles)>       </cfif>    <cfelse>       <cfset user = createObject("component", "appbooster.model.user.user").init()>    </cfif>    <cfreturn user> </cffunction>

So, in this case, we are getting a user and his associated security roles. You can see all the logic for handling this lies inside the service component, making it easy to maintain while improving reusability and portability. Why? Well, for example, because a user service is something I may commonly use throughout different applications, and therefore I may reuse/repurpose this userService.cfc in multiple applications. Also, this service object would look exactly the same regardless of what framework it existed on.

So, hopefully this has served as a good general introduction to the service layer and now you are imagining your controller/listener just calling a bunch of service components that are loaded in the cunstructor/init (or setters). These service components call DAOs and Gateways, which I also set in the cunstructor/init (or setters). You may be asking, doesn't it start to become a lot of trouble maintaining all these dependencies? Aha! Hello ColdSpring!..but that is a discussion for another post :)

Comments

Peter J. Farrell Very nice post Brian!

Posted By Peter J. Farrell / Posted on 09/17/2006 at 9:43 PM


Antony Nice post. I can't wait for the &quot;discussion for another post&quot; to start :-)

Posted By Antony / Posted on 09/17/2006 at 10:38 PM


Peter Bell Hi Brian,

Nice post!

To enforce &quot;service as API&quot; it is often worth making all DAO and Gateway methods &quot;package&quot; (protected for the Java heads) and putting UserService.cfc, UserGateway.cfc, UserDAO.cfc and UserObject.cfc in the same directory.

Oh, but could you simplify the captcha?! Pete provides an XML file so it is really easy to do and Charlie has provided step by step instructions . . .

http://carehart.org/blog/client/index.cfm/2006/8/17/simplifying_lyla_in_blogcfc

Looking forwards to the ColdSpring post!!!

Best Wishes,
Peter

Posted By Peter Bell / Posted on 09/18/2006 at 8:00 AM


Rob Gonda Sometimes it's easier to see it in a picture, so I uploaded a snapshot of Dave Ross's ColdSpring presso @ CFUnited.

http://www.robgonda.com/blog/files/robGonda/UserFiles/Image/layers%20architecture.jpg

Cheers.

Posted By Rob Gonda / Posted on 09/18/2006 at 7:23 PM


Aaron Brian,

I think this was a great if it was written with the sole purpose of arguing for the benefits of using a service layer.

I think I understand service layers, and was benefited by the post, but I believe you originally set out to introduce these concepts to beginners in this series. If I didn't already know what a service layer was and how to use it this article would have made me want to learn how to code up a service layer due to the benefits so nicely articulated, but I would still be at a loss for how to write one for my own apps.

-Aaron

Posted By Aaron / Posted on 01/18/2007 at 9:07 AM


Brian Rinaldi Aaron, Thanks for the comment. There isn't much to coding a service layer, honestly, so I think understanding what it is about is the most effective way for you to know how to code it. I mean in the end (in CF) it is simply a collection of components with various methods that create the API for different aspects of your application. The specifics of how these methods are implemented is up to you.

Nonetheless, if you want to see the basic skeleton of a service layer component, check out my code generator which will auto-generate some very basic service methods for you - http://code.google.com/p/cfcgenerator/

Posted By Brian Rinaldi / Posted on 01/19/2007 at 5:32 AM


LT Hi, Brian.

Thanks for the post. I learned the hard way myself - a very bloated controller in MG.
I'd love to see an intermediate or advanced version of this article. Do you think you might do this in the future?

LT

Posted By LT / Posted on 02/12/2008 at 1:11 PM


Brian Rinaldi @LT - I had not considered it, but now that you mention it, I will try to do that.

Posted By Brian Rinaldi / Posted on 02/13/2008 at 8:54 AM


Write your comment



(it will not be displayed)





About

My name is Brian Rinaldi and I am the Web Community Manager for Flash Platform at Adobe. I am a regular blogger, speaker and author. I also founded RIA Unleashed conference in Boston. The views expressed on this site are my own & not those of my employer.