IoC and Lazy Libraries

Okay, so I'm finally going to post something actually related to the framework again. :)

Here's the deal. I've been aware for a while that folks have been unhappy with some of the features in the onTap framework, in particular the fact that there are a number of places where code is executed outside of CFCs. This is done in the framework for very specific reasons, and I know for a fact that it's actually more flexible than using CFCs (think "Fusebox lexicon" and you should start to get an idea why). I also believe (although I don't know) that it's not significantly more "dangerous" (see my articles about duck-typing and about descriptive variable names).

Still it's difficult for people (myself included) to see past their knee-jerk responses to something that looks unusual or seems like a bad practice to actually understand the problems it solves.

So in recent weeks, largely because I was already migrating the ORM components out to DataFaucet, I started making some pretty radical changes to the onTap framework. This is good news on several fronts. First if you're already using it, you don't need to worry, because the system was so well encapsulated already (believe it or not, yes, yes it was), that your code shouldn't change by more than a handful of lines. In spite of the fact that I'm taking a few weeks to make these changes, *your* changes shouldn't take more than a few minutes. Secondly the new system is adding CFCs in some critical places and in at least one place this will make the system much more tweakable. It's certainly going to improve on its already very effective encapsulation.

First, the _appsettings.cfm that has confused people for a long long time as the initial method of configuring the framework is going away. It's being replaced by a config.cfc in the application root directory that extends tap.cfc which loads the framework core. This leads to some simplifications of some previous features like application-specific mappings. Want another mapping? Simple.

<cfcomponent displayname="config" extends="tap">

<cffunction name="configure" access="private" output="false">

<cfset addMapping("datafaucet","../datafaucet") />

</cffunction>
</cfcomponent>

Is that a relative path?! In a mapping? Yes it is... and it works. the addMapping() method allows you to specify mappings either absolute or relative to the framework root directory. How does it know? If it finds a directory on the relative path, then it's relative, otherwise it's absolute. Also the addMapping() function automatically prepends the required "/" at the beginning of the mapping string, which has been the source of LOTS of confusion about how precisely to create mappings in ColdFusion 8. Which means this method of adding mappings is much simpler and more straightforward than Application.cfc.

The code for path-settings is also going to become CFCs and there will be a similar, new _mappings directory for the addition of 3rd-party mappings (which should be used sparingly, but will be an important enhancement to the framework's plugin manager).

Now on to the 2 features I mentioned in the article title. I've already added lazy-libraries. In the past the framework has had a bit of a heavy initial load because it loaded its entire set of function libraries on application start. Going all the way back to the ColdFusion 5 days there had been this notion of having a library of functions (DLL wouldn't be an entirely bad analogy), which each understood their own dependencies and could then load those dependencies when they themselves loaded.

Sweet! At least in theory. In practice however loading them always turned out to be a pain. When / where do you load them? Which ones need to load on app start? Which ones can be deferred? Have I loaded x on a given page request? Should I load it on request start? ::sigh:: leading me ultimately to make the framework load them all by default, which was ugly and had some of its own problems aside from the initial load time.

A while back I had this notion of creating a "lazy loading" library out of them, where all those functions before had been in a simple structure, they would become part of a CFC and then could be loaded dynamically as-needed using the new onMissingMethod() feature in ColdFusion 8. I had actually even started working on that change a while ago and ended up giving up and rolling the change back in SVN because I had too many problems implementing it at the time. Well now I've gone back and revisited the idea and I've managed to overcome all the challenges I found before. :)

Sweet! So now you never have to worry about whether or not a particular function is loaded because the library will load it for you when it needs it. And there's an extra bonus that the framework loads faster! I've also added internal references in the libraries themselves so that instead of calling request.tapi.OtherMethod() the internal method calls become lib.OtherMethod() -- using the internal pointer. Granted that if you're calling a method in the same library, it can just use this.OtherMethod() -- lib.OtherMethod() just allows you to traverse the library from the root (similar to the way the default / mapping in ColdFusion lets you traverse files from the web root).

The core onTap.cfc also got a new getLib() method for returning the framework's core library, so that library calls from CFCs can be "composed" rather than referencing the request scope directly.

The only down side to this change is that due to a limitation in the getCurrentTemplatePath() method caused by inconsistency in its behavior (which the CF team last I knew refused to acknowledge), the file methods had to be moved outside of the main library to a new request.fs. structure. Those functions can't be stored in a CFC -- no way, no how. There simply is no workaround that allows them to exist in a CFC. Unfortunately... I believe I've also added getFS() to the ontap.cfc to fetch the file library in the same way that the core library is "composed". This way if at some point in the future it becomes possible to move that library back into the lazy library, then getFS() can just return the getLib() pointer and the getFS() method can be deprecated. So that should be good forward-looking.

If you're using the file.cfc to manage your files, then you don't have anything to worry about with this change. You might need to do a little extra leg-work if you were calling a lot of request.tapi.fileRead() or the like -- you'll just have to change those references, which shouldn't be too difficult.

Lastly I haven't started working on it yet, but I'm planning an IOC-MANAGER for the next release. I say "manager" because although it will have a default IOC package, the system is going to be designed specifically for the purpose of managing multiple IOC packages from different applications / plugins. So where for example ColdBox has just an IOC-type (coldspring/lightwire) to integrate a single IOC configuration, the onTap framework will instead have an addIOC() method for attaching arbitrary IOC packages, and a facade for allowing you to write your own IOC-wrappers (although I plan to include one for ColdSpring and one for LightWire out of the box). Your application code can then either getIOCManager().getBean("name","DataFaucet") for example or it can getIOCManager().getIOCPackage("DataFaucet").getBean("name"). Although I would encurage not doing those all in the same function and instead compose separate getIOCManager(), getIOCPackage() and getIOCBean() functions in your own components. That will minimize the amount of code you need to write later if anything in the core application needs to change - OR - if you later decide to migrate to a different framework.

Related Blog Entries

Comments
Mark C's Gravatar Isaac, I really, really hope you have good health insurance, eat right, exercise regularly, and generally look after your well being. Why? Because you are onTap.
# Posted By Mark C | 7/11/08 10:04 AM
ike's Gravatar Thanks Mark.
Nice domain name by the way. :)
# Posted By ike | 7/11/08 12:11 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.5.006. | Protected by Akismet | Blog with WordPress