Categories
Contact Doug!
Learn About Doug!
View Doug Boude's online resume
updated 11/18/2009

View Doug Boude's profile on LinkedIn
Link to me!

Follow Doug Boude on Twitter
Follow me!

Be Doug's friend on Facebook
Befriend me!
(I promise not to follow you home)
OO Lexicon
Chat with Doug!
Recent Entries
You may also be interested in...
Web Hosting

<< May, 2013 >>
SMTWTFS
1234
567891011
12131415161718
19202122232425
262728293031
Search Blog

Recent Comments
Re: Disappearing IE Popup Window During Save/Open Dialog (by LZ at 4/20 7:58 AM)
Re: Create Dynamic WHERE Clauses in PHP (by pooja at 3/20 7:29 AM)
Re: Just What IS a 'Service Layer', Anyway? (by EugenK at 3/07 7:56 PM)
Re: Using Google as your CF Mail Server (by 5starwebteam.com at 2/25 1:27 AM)
Re: Why Provide for Service layer objects in CFWheels? (by Steven Benjamin at 1/25 11:43 AM)
Re: What is an 'Advanced' Coldfusion Developer? (by ColdFusion Developer at 12/24 5:14 AM)
Re: Equivalent of SQL "TOP X" in Oracle (by Ashenafi Desalegn at 12/06 5:29 AM)
Re: PHP Export to Excel Snippet (by serene at 12/05 1:44 AM)
Re: Just What Is 'Application Logic', Anyway? (by Arif at 11/13 8:06 AM)
Re: Hosts File Changes Not Acknowledged on Vista 64 (by Aaron at 10/22 2:31 PM)
Re: PHP Export to Excel Snippet (by Jafar Shah at 10/10 4:28 AM)
Re: Viewing Option Text (in IE7) that's Wider than the Select List (by Chenelle S at 10/04 12:53 PM)
Re: PHP Export to Excel Snippet (by Kilo at 9/26 5:20 PM)
Re: Porting Coldfusion Code to Mura (by tariq at 9/03 9:51 AM)
Re: Just What IS a 'Service Layer', Anyway? (by James at 8/27 4:06 PM)
Re: Calculating Business Hours (by helen at 8/14 2:54 AM)
Re: What IS 'Business Logic', Anyway? (by dougboude at 8/06 11:30 AM)
Re: What IS 'Business Logic', Anyway? (by Adrianne at 8/06 10:29 AM)
Re: Family Law: The Weapon of Choice for Woman Scorned (by dougboude at 8/04 4:39 PM)
Re: Family Law: The Weapon of Choice for Woman Scorned (by Lola LB at 8/04 7:43 AM)
Archives
Photo Albums
Funnies (5)
Family (3)
RSS

Powered by
BlogCFM v1.11

18 April 2012
Providing for Service Layer Objects in CFWheels: Hello Wirebox!
Post 3 of 3

This is the third and final post in a series on making the case for service layer objects (in CFWheels or any framework for that matter!), and two means of implementing them. The second post demonstrated one approach to manually implementing SLOs. This post will demonstrate my MUCH preferred approach: utilizing Ortus Solutions' Wirebox DI/IOC framework.

DI/IOC REVIEW

By way of quick review, the purpose of a Dependency Injection/Inversion Of Control framework is to give the developer ONE PLACE in his app where he can manage the objects that are players within his or her app. For instance, you may have a dataImport object which itself, internally, must be able to communicate with the persistence layer (session, typically), create and manipulate users via a CFWheels User model object, and perhaps access some global utilities via a Utilities object. The manual approach requires you to create and pass in these objects at the time you create your dataImport object. Using Wirebox, you simply ask for the dataImport object, and because you have already pre-defined what it's requirements are, Wirebox worries about building it for you. The advantage? When the app's architecture evolves, and/or object interfaces change, the developer can make those changes in a single spot: the wirebox configuration cfc.

Assuming that at this point you understand why DI/IOC is important, and assuming that you understand the common need to utilize objects in your CFWheels controllers that are NOT directly associated with a database table, allow me to dive in to how I added SLO functionality to CFWheels using Wirebox and the Wirebox Plugin.

Steps I took/Modifications I Made

1. Created a Services folder off the root;
2. Dropped the Wirebox framework off the root;
3. Dropped the Wirebox Plugin zip file into my Plugins directory;
4. Modified the config/wirebox_config cfc so that it knew how my service layer objects should be prepared

That's it! Let's peek at these items in more detail. Well, at least, the ones that aren't already self-explanatory. Oh, I guess that would only be the last step!

In step 4, I needed to tell Wirebox about my service layer objects, the values they require when being created, and the relationships between them. Wirebox provides two basic ways to do this: By annotating your CFCs using the Wirebox Domain Specific Language, or using dependency method chaining. Let me show you a quick example of doing the same thing using each approach.

Let's say you have an object that you want to be given your app's datasource name when it is created. You would manually pass in this value in the object's init method via an argument, so let's go that route. Using Wirebox annotation, you would do this:

 

<cffunction name="init">
 <cfargument name="dsn" type="string" required="true" inject="wheels:setting:datasourcename" />
 <cfset setDSN(arguments.dsn) />
 ....

 
Of note there is the "inject" attribute; Wirebox inspects your CFC before instantiating it and knows that when it does, it should retrieve and pass in the wheels datasourcename during init.

 

Here is what it looks like, in your wirebox_config CFC, using the dependency method chain:

 

<cfset mapPath("services.dataImport").initArg(name="dsn",dsl="wheels:setting:datasourcename");

 

Personally, I prefer (and recommend) the latter method. It keeps your CFCs "normal" (no custom attributes), and the dependency method chain is much easier to read and interpret, all in one line.

Once you have completed step 4, you can write controller methods such as this:

 

<cffunction name="importUsers">
  <!--- perform the data import routine for incoming data... --->
  <cfset result = service("importService").importData(params.dataIn) />
 
  <cfset flashInsert(msg=result) />
  <cfset redirectTo(action="index") />
 </cffunction>

 
Notice the "service()" method; this is what you will use when you want to retrieve a SLO as opposed to a Wheels Model object.

 


DEPENDENCY METHOD CHAIN DETAILS

What good would it be to be able to use SLOs in CFWheels, but those SLOs not be able to use Wheels Model objects internally?? Not much at all. Using the Wirebox plugin, you have the option of enabling your SLOs to have access to Wheels Models. Let's examine a fairly complex Dependency Method Chain definition for the importData SLO.

  

//define our import service layer object
   mapPath("services.importService")
    .asSingleton()
    .initArg(
     name="dsn",
     ref="dsn")
    .initArg(
     name="messageprefix",
     dsl="wheels:setting:messageprefix"
    )
    .property(name="sessionService",ref="sessionStorage")
    .mixins(wheelsORM);//give this object the ability to access Wheels models

 

The first method, 'mapPath', tells wirebox to create an object called 'importService', the CFC for which is found in the services folder.

Next we chain on 'asSingleton()' so that Wirebox knows there should only ever exist one instance of this object.

'initArg' we saw already; we are telling Wirebox that this CFC's init method requires an argument named "dsn", and we want to pass the value contained in the reference ('ref') named "dsn". More on this in a moment.

A second initArg method is chained onto that. Can you imagine what the CFC's init arguments look like? I bet you can. For this initArg(), Wirebox is being told to utilize the Wheels-specific Domain Specific Language (part of this plugin) to retrieve the setting for "messageprefix" (probably being set in config/settings.cfm) and pass that in.

'property()' is telling Wirebox that we want to inject into the object's variables scope, after it has been initialized, a variable named sessionService that is an instance of the sessionStorage object. Again, more on the 'ref' key in a moment.

Lastly, we are telling Wirebox via the 'mixins()' method that we wish for this object to have the ability to call Wheels' 'model()' method internally.Okay, the 'ref'. Also in our configuration cfc we created a value named "dsn" that we can then REFerence in other object definitions. It looks like this:

 

map("dsn").toDSL("wheels:setting:datasourcename");

 

By doing that, any other object that needs the datasourcename value passed in, we can simply say ref="dsn". Same with the sessionStorage service. Elsewhere in our configuration cfc we defined it as

 

map("sessionStorage").to("services.persistenceCFC").asSingleton();

 

If we want to inject that object, now we only need refer to it by 'ref'.

Hopefully that quick overview, and the link to the working data import sample app that uses Wirebox will get you started. The plugin has not yet been approved for inclusion on the CFWheels site, but if you want to play with it now there's a link to it as well. The plugin and the app both contain a READMEDUDE.txt file that you should check out before you do anything else.

Thanks for tuning in!

--------------------------------------

Video demo on youtube

Thorough Wirebox Documentation

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)

Wirebox Plugin (again, be sure to visit it's READMEDUDE.txt)

Posted by dougboude at 11:50 AM | PRINT THIS POST! | Link | 6 comments



13 April 2012
Providing For Service Layer Objects in CFWheels
Post 2 of 3

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:

<cfset initServices() />

 

 

 

 

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.)

Posted by dougboude at 7:39 PM | PRINT THIS POST! | Link | 4 comments
12 April 2012
Why Provide for Service layer objects in CFWheels?
Post 1 of 3

It's been probably 3 months since I started using the CFWheels framework, and I have nothing but praise for it. It's a compact conventions-based framework patterned after Ruby on Rails that, once you have a grasp of the relatively simple yet complete (mostly) API, makes putting an app together a pleasure. Having come to CFWheels from other frameworks that I've been using for a few years now (Model Glue and Coldbox primarily), I was a bit taken aback when I discovered that not only was there no provision made in CFWheels for utilizing service layer objects, but every other CFWheels developer I communicated with about the apparent lack gave me the virtual two dollar look and wondered why on earth I would ever need a service layer object in my application! Apparently, or so it seems to me thus far, I am the ONLY CFWheels developer on the planet who ever saw a service layer object as a necessity! Now, I don't believe that is true (how could it be?), but since I've encountered no others, I felt the need to do a short series of blog posts covering my view of service layer objects, why they're potentially necessary in ANY app (including a CFWheels app!), and two ways in which I overcame CFWheels' ostrecization of service layer objects.

This post will cover my personal philosophy on service layer objects and use cases for them. I'll cover the remaining information in subsequent posts.

note: my personal belief on the necessity of service layer object only holds water IF a person, like me, holds religiously to the philosophy that controllers as a rule should remain as thin and code-free as possible, and that as much business logic as possible belongs in the app's model, not in its controllers. Additionally, that the controller's job is to act as liason between the view and the model, nothing more.

SO, what is a service layer object? I evolved my personal definition of this creature literally years ago, and in all my subsequent experience with OOP, that definition has not changed one iota. A service layer object is an object which utilizes one or more child or even sibling objects in order to encapsulate and perform some genre of work for your applicaton. My favorite analogy is that of a man in an easy chair with three remotes on the tv table beside him. His wife (the application) commands him to "start the movie" (calls his "startMovie()" method). The man (the service layer object, "objHusband") picks up the TV remote (objTV) and turns it on (objTV.powerOn()), changes the input source (objTV.changeInputSource(3)), then adjusts the volume (objTV.adjustVolume(15)). He then takes the DVD remote (objDVD) and powers it on (objDVD.powerOn()), and starts the movie (objDVD.Play()). This husband provided his wife with a single API method ("startMovie()"), while he himself (objHusband) internally manipulated at least two other autonomous class instances (objTV and objDVD) in order to accomplish that piece of work. THAT is the beauty, purpose, and reason for, service layer objects. Without the husband, the application/wife would have had to talk to two other objects and manipulate them herself...heaven forbid! And, if at any point in time the objTV had been replaced and the buttons weren't the same, the wife wouldn't be able to start her own movie at all! But with the Husband service layer, she doesn't ever need to care about how to work the TV remote...that's the service layer's job, and that is where any and all code related to TV manipulation live and would be modified if the need arose. It's beautiful, isn't it? And if the solution is beautiful, it is right.

Now, where in the real world of apps does this same situation occur? In my world, and I KNOW it must be so in your world too, it occurs ALL THE TIME! As an example, allow me to share the skinny on an app I wrote a while back. This app is a tool that allows multiple users belonging to multiple different client subscriber companies to upload their spreadsheet full of insurance claims into a third party system. This third party system has a rather painful upload lifecycle that requires that each claim be in the form of an XML file that conforms to their DTD. Each xml file/claim is then FTP'd to a test server where, after some unknown time period, their cron job comes along and parses it to test for errors. The cron job leaves behind a text file named the same as the xml file indicating if it passed its test, and if not, what errors were encountered. My app has to poll for these test results, grab them, parse them, and return meaningful information to the user. If the xml file passes, it is then allowed to be FTP'd to the third party's production server where the same process takes place again. That's it in a nutshell. Ugly, eh? But even with such a complicated, ugly process to have to deal with, it can be managed well code-wise if we carefully group like functionality together and encapsulate it in a logical way. Working through the process/code needs to accomodate the process described, you would (as did I) come up with an architecture that is composed of several very purposeful, focused CFCs, and a few higher level CFCs that incorporated and manipulated these more specific CFCs (this is called "aggregation"; my service layer component "aggregates" my single-focus CFCs). My own solution resulted in the following CFCs, all of which reside within my app's "model" folder:

  • user - represents a system user
  • keychain - represents a user's system roles and permissions
  • POIUtility - a utility (modificaton of Ben Nadel's POI Utility) for parsing and manipulating Excel files
  • profile - simple object for retrieving client data profiles
  • spreadsheet - retrieves client spreadsheet definitions and mappings between spreadsheet columns and XML nodes
  • token - utility cfc to translate placeholder tokens into appropriate values
  • fileModel - utility to manage the upload, retrieval, parsing, and db logging related to files sent to the 3rd party system
  • wormhole - persistence layer object used to allow other objects to communicate with the persistence layer (session, etc.)
  • XMLService - service layer object that uses token and profile (and other settings) to translate incoming claim records into proper XML files for upload.
  • spreadsheetService - service layer object that utilizes POIUtility, spreadsheet, and XMLService to do its work (look! A service that relies on another service!)

 

 

 

I won't delve into any more detail than this, but suffice it to say that, there is a LOT Of work that is going on that does NOT relate directly to ANY DATABASE TABLE! Now, if a CFWheels developer (any of the ones I have communicated with) were to write this same app using that framework, there's only a few probable things that could occur: They would end up with controllers that are bloated beyond belief, possibly trying to call one another for different functionality, OR they would have created a whole slough of UDFs somewhere and had to include them in their controllers, OR they would have had to write CFWheels plugins to handle the meatier parts of the work. Any of those three approaches, to me, are either: wrong, ugly, far more work than should be necessary, or any combination thereof. CFWheels, like any other app, SHOULD provide for easily implementing service layer objects, not force developers to work around this fact! I should be able, within my controller OR from within my service layer object, be able to call "<cfset objSpreadsheetService = model("spreadsheetService"), and NOT get an error telling me there's no corresponding table! That's my belief, anyway.

So, based on my personal OOP beliefs, and using this real world example as my use case (though I have several others as well), I conclude that there exists a definitive need for CFWheels to accommodate service layer objects and to expand its definition of what an app's model truly is, as encompassing not only the CFCs representing discrete database tables, but also any and all other business logic that is unique to the application's identity.

 All that said, I do realize that CFWheels is patterend after ROR, and typically when I ask questions like this, the first response is to ask "how does RoR handle that"? I don't know the answer to that, and perhaps because this IS a RoR-patterned framework I shouldn't even WANT it to accommodate service layers as I know them to be. In any event though, I DID convince CFWheels to allow me to code the way I want to. Coming up next, the second post in this series covering the first approach I used to get CFWheels to respect my need for service layer objects.

Next: Providing for Service Layer Objects in CFWheels

Posted by dougboude at 10:48 AM | PRINT THIS POST! | Link | 5 comments