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:
<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:
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:
<!--- 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.
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:
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
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!
--------------------------------------
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)
You are not logged in, so your subscription status for this entry is unknown. You can login or register here.
I didn't document all of the possibilities using the "wheels:setting:[some setting]" DSL because...any setting that you can normally access using Wheels' "get()" method, you can use in place of the [some setting] placeholder! I couldn't think of anything else besides settings that you might want or need to pass into a SLO from Wheels, but if you know of anything else (maybe plugins or something?) I can modify the Wheels DSL to accommodate it.
Make sense?
