Search my blog:
Viewing By Entry / Main
September 17, 2006

Objects and Frameworks - The Service Layer

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

Very nice post Brian!


Nice post. I can't wait for the "discussion for another post" to start :-)


Hi Brian,

Nice post!

To enforce "service as API" it is often worth making all DAO and Gateway methods "package" (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


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.


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


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/


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


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