In my previous post in this series, I made the case for why I believe that service layer objects are a common need in an application's architecture. I also pointed out the fact that CFWheels does not provide for such animals within its framework, and that I had overcome this obstacle using two different approaches. What follows are the details of my first approach and a simple yet complete working sample app for your dissection pleasure.
MANUALLY IMPLEMENTING SERVICE LAYER OBJECTS IN CFWHEELS
So, what we need to be able to do, ideally, is have and use CFCs in our controllers that are NOT directly associated with a database table. The CFWheels approach requires all models to extend the Model CFC. If we omit that extension, and then attempt to retrieve our service layer object using the "model()" method call, we receive an error. If we DO extend the Model CFC on a service layer object and attempt to retrieve it using the "model()" method call, we receive an error. What to do, what to do!
In a nutshell, we have to instantiate and retrieve our service layer objects ourselves. This could simply be done using a line in a controller that performs a "createObject" call, and that would give us our object. Ah, but one of the key elements that a CFWheels developer needs is still missing, because although I can indeed create an instance of a service layer object this way, my service layer object will be completely ignorant of the CFWheels environment. It won't be able to do one of the things it needs to, and that is to ITSELF utilize the CFWheels "model()" and "get()" calls! The remedy for this turned out to be simple, but took a LOT of digging to discover: include the appropriate files from the CFWheels framework in my service layer objects.
Personally, I opted not to show a demo of implementing SLOs (I'm gonna use this acronym from here out) the way described above, because it just wasn't beautiful nor did it provide for an easy way of re-use in multitple controllers. What I decided I wanted, then, was a method all my own that I could use within controllers in order to retrieve my SLOs. Since I retrieve models using 'model("somemodel")', I thought it appropriate to retrieve my SLOs using 'service("mySLO")'. Additionally, I didn't want my SLOs living in the same folder as my models, so I create a folder just for them called "services". This folder contains a core SLO base object, exactly the same way that the "models" folder contains a core Model object. All of my SLOs extend this core component, and thusly inherit the required CFWheels functionality they need to do their jobs.
Steps I took/Modifications I Made
1. Created a "services" folder off the root;
2. Created a core "Service.cfc" that all SLOs must extend;
3. Modified my core "Controller.cfc to include two new methods;
That's it! Let's peek at these items in more detail.
In step 2, it was convenient (and followed the same approach as the rest of CFWheels) to create a core component to be extended. The entire contents of this component is as follows:
<!---
This CFC provides access to the wheels core functions needed by our service layer objects.
--->
<cfcomponent output="false">
<cfinclude template="../wheels/global/functions.cfm">
</cfcomponent>
In step 3, I added two methods to my core Controller.cfc. One of these methods, you may have supposed, is called "service". The other provides a single place where the developer can declare and create all of their SLOs at once. That method is called "initServices", and it looks like this:
<cffunction name="initServices" returntype="void" hint="I initialize the services objects for this app">
<!--- create readable alias keys that the dev will use to get these objects --->
<cfset application.$_ServiceObjects = {
importService = createObject("services.importUsers").init(
dsn = get("DataSourceName")
),
sessionStorage = createObject("component","services.wormhole").init()
} />
</cffunction>
The "service" method looks like this:
<cffunction name="service" returntype="any" hint="I am the method used to access any service layer object from within any controller">
<cfargument name="service" type="string" required="true" />
<cfset var retval = "" />
<cfif structkeyExists(application.$_ServiceObjects,arguments.service)>
<cfset retval = application.$_ServiceObjects[arguments.service] />
</cfif>
<cfreturn retval />
</cffunction>
One more tiny little thing, within the Controller.cfc's Init method, I added a call to InitServices to kick it off:
That is IT. In this example, what I am able to do now that I could NOT do before in a CFWheels controller (without bloating my controller, anyway) is this:
<cffunction name="importUsers">
<cfset var objImporter = service("importService") />
<cfset result = objImporter.importData(params.dataIn) />
<cfset flashInsert(msg=result) />
<cfset redirectTo(action="index") />
</cffunction>
THAT, my friends, is how thin a controller method SHOULD be, WHENEVER possible!
Okay, I won't blab on about this approach. I think it's slick, it's fairly easy to maintain, and I think once you take the time to poke through and run the sample user data importer app linked in this post, that you'll agree that you really have been missing Service Layer Objects too, you just didn't call it by that name.
Doug out :0)
Next up in the series: Implementing Service Layer Objects in CFWheels using the awesome DI framework WIREBOX and the plugin I wrote for it! :)
--------------------------------
Sample App Zip File (be sure to read the READMEDUDE.txt in the zip; you'll have to create one table and edit the datasource setting to get the app to work for you.)
