By Peter Bell

How to Write your own Dependency Injection Engine!

Since I'm writing my own Dependency Injection engine (LightWire), I though I might as well blog about the process and the design decisions in case anyone is interested in the thinking required to create a simple DI engine. If anyone notices any design flaws, I’d love to hear about them!!! You can follow along with the code (it’ll be updated before tomorrow morning) at http://lightwire.riaforge.org using either the zip file or the subversion repository.

[UPDATE] Just added all the code to this posting as well, so you can follow along without having to download files!

Init()
The init() method is fairly simple. It just needs to set all of the properties in the variables scope and then if lazy loading is off, it needs to loop through all of the singletons and if they don’t already exist in the variables scope in LightWire, it should create them. How would they already exist? Well, because each singleton may be dependent on other singletons. Every getObject() call creates not only the requested object, but any of its dependencies, so even when initializing LightWire, it is quite likely that by the time you get to some of the later singletons, they will already exist.

<cffunction name="init" returntype="LightWire" access="public" output="true" hint="I initialize the LightWire object factory.">
   <cfargument name="LightWireConfigFilePath" required="yes" hint="I am the fully qualified cfinclude path to the configuration file to include - including the file path, file name and file extension.">
   <cfset var key = "">
   <!--- Include all of the class configuration properties --->
   <cfinclude template="#arguments.LightWireConfigFilePath#">
   <cfparam name="variables.Singleton" default="#StructNew()#">
   <cfparam name="variables.LightWire.LazyLoad" default="1">
   <cfscript>
      If (NOT variables.LightWire.LazyLoad)
      {
         // Loop through every bean definition          For (key in variables.Bean)
         {
             // Create every singleton              If (variables.Bean[key].Singleton)
             {
               if(not StructKeyExists(variables.Singleton,key))
               {
                  // Only create if hasn't already been created as dependency of earlier singleton                   variables.Singleton[key] = variables.getObject(key,"Singleton");
               };
             };
         };
      };
   </cfscript>
   <cfreturn This />
</cffunction>

The Configuration File
I tend to prefer programmatic config files for a number of reasons (it is well worth reading both that section and the whole article). That said, I understand the benefits of XML configuration files as well. For now I’ve decided just to provide support for a programmatic configuration file and I’ll add a simple translator for reading an XML file or object when someone asks for one (I’ll base the XML off of the Java Spring DTD).

The programmatic configuration file is very simple indeed with the following system settings:

Lazy load - If this is set to 1, LightWire will just load components and their dependent components as required. If 0, LightWire will load all of the singletons in a project on first being called. Lazy loading speeds up first page load for complex applications – especially when testing. Recommended setting: 1 (lazy load on). Please note that lazy loading is only available at a system level – not a bean level.
e.g. variables.LightWire.LazyLoad = 1
Base class path - The base class path allows you to more easily reuse configurations between applications. Instead of having to change the class path for every single bean, you can just change the base class path in one place (and you can even set it using a variable if the base class pass is always #application.name#.com or something similar – one of the benfits of programmatic config files).
e.g. variables.LightWire.BaseClassPath = "myapp.com";

Then for every bean in the application, you need to create a structure under variables.Bean with the following required and (if wanted) optional properties:
Required
Singleton - Whether the bean is a singleton or a transient. 1 = singleton.
e.g. variables.Bean.UserService.Singleton = 1;
Path - The path to the bean relative to the base class path. Can be blank if it is in the base class path directory.
e.g. variables.Bean.UserService.Path = "model.user";

Optional
Constructor dependencies - An optional comma delimited list of the names of any beans that need to be passed to the init() method of the bean.
e.g. variables.UserService.ConstructorDependencies = "UserDAO,UserGateway";
Setter dependencies - An optional comma delimited list of any bean names that need to be injected using setter injection. e.g. variables.testService.SetterDependencies = "CompanyService";
Mixin dependencies - An optional comma delimited list of any bean names that need to be injected using mixin injection.
e.g. variables.UserService.MixinDependencies = "ProductService";
Constructor Properties - An optional struct containing any non-bean constructor properties required by the init() method. Can contain any simple or complex data types within the struct which is passed into the init() method using ArgumentCollection after having any constructor dependencies (beans) added automatically.
e.g. variables.UserService.ConstructorProperties.PropertyName = "This Value";

Sample config file:

<cfscript>
   // LIGHTWIRE PARAMETERS    // LazyLoad - Should LightWire use lazy loading or just load all singletons on startup?    // SYNTAX: variables.LightWire.LazyLoad = [1/0];
   variables.LightWire.LazyLoad = 0;
   // Base Class Path - The base path including mapping to your components directory    // SYNTAX: variables.LightWire.BaseClassPath = "myproject.com";    variables.LightWire.BaseClassPath = "lw2.com";
   
   // LIGHTWIRE BEAN DEFINITIONS    // CLASS PATH (Required: needed for every Singleton and Transient class - may be of zero length)    // Syntax: variables.Bean.[ClassName].Path = "path"    // Example: variables.Bean.PageService.Path = "custom.model.Page";
   // CONSTRUCTOR DEPENDENCIES (only needed if the object has one or more constructor dependencies)    // Syntax: variables.Bean.[ClassName].ConstructorDependencies = "[comma delimited list of class names it depends on]";    // Example: variables.Bean.PageService.ConstructorDependencies = "PageDAO,UserService";
   // SETTER DEPENDENCIES (only needed if the object has one or more setter dependencies)    // Syntax: variables.Bean.[.ClassName]SetterDependencies = "[comma delimited list of class names it depends on]";    // Example: variables.Bean.PageService.SetterDependencies = "PageDAO,UserService";
   // MIXIN DEPENDENCIES (only needed if the object has one or more mixin dependencies)    // Syntax: variables.Bean.[ClassName].MixinDependencies = "[comma delimited list of class names it depends on]";    // Example: variables.Bean.PageService.MixinDependencies = "PageDAO,UserService";    
   // CONSTRUCTOR CONFIGURATION PROPERTIES (If you'd like to be able to set them here as opposed to in the class init files)    // Syntax: variables.Bean.[ClassName].ConstructorProperties = "[struct containing all properties required except for beans]";    // Example: variables.Bean.PageService.ConstructorProperties.MyProperty = "MyValue"; </cfscript>

Creating Objects
Whether you are lazy loading or not, getObject() is the private method that does all of the heavy lifting. It is called by the init() method (if you are not lazy loading) and by LightWire’s two other public methods: getSingleton() and getTransient(). Each takes the name of an object and calls getObject(ObjectName,”Singleton”) or getObject(ObjectName,”Transient”) respectively. Both return a fully loaded object with all of its dependencies resolved and injected (constructor, setter and mixin).

<cffunction name="getSingleton" returntype="any" access="public" output="false" hint="I return a LightWire scoped Singleton with all of its dependencies loaded.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to generate.">
   <cfscript>
      // If the object doesn't exist, lazy load it       if(not StructKeyExists(variables.Singleton, arguments.ObjectName))
         variables.Singleton[arguments.ObjectName] = variables.getObject(arguments.ObjectName,"Singleton");
   </cfscript>
   <cfreturn variables.Singleton[arguments.ObjectName] />   
</cffunction>

<cffunction name="getTransient" returntype="any" access="public" output="false" hint="I return a transient object.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to create." />   
   <cfreturn variables.getObject(arguments.ObjectName,"Transient") />
</cffunction>

<cffunction name="getObject" returntype="any" access="private" output="false" hint="I return a LightWire scoped object (Singleton or Transient) with all of its dependencies loaded.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to return.">
   <cfargument name="ObjectType" type="string" required="yes" hint="I am the type of object to return (Singleton or Transient).">
   <cfscript>
      // Firstly get a list of all constructor dependent singleton objects that haven't been created (if any) - n levels deep       var ObjectstoCreateList = variables.getDependentObjectList(arguments.ObjectName);
      var LoopObjectName = "";
      var TemporaryObjects = StructNew();
      var Count = 0;      
      var ListLength = 0;      
      var ReturnObject = "";      
      var LoopObjectList = ObjectstoCreateList;      
      
      // Then create all of the dependent objects       while (ListLen(LoopObjectList))
      {
         // Get the last object name          LoopObjectName = ListLast(LoopObjectList);
         // Call createNewObject() to create and constructor initialize it          variables.Singleton[LoopObjectName] = variables.createNewObject(LoopObjectName,"Singleton");
         // Remove that object name from the list          ListLength = ListLen(LoopObjectList);
         LoopObjectList = ListDeleteAt(LoopObjectList,ListLength);
      };
      // Then create the original object       ReturnObject = variables.createNewObject(arguments.ObjectName,arguments.ObjectType);
      // And if it is a singleton, cache it within LightWire       If (arguments.ObjectType EQ "Singleton")
         variables.Singleton[arguments.ObjectName] = ReturnObject;
         
      // Then for each dependent object, do any setter and mixin injections required       LoopObjectList = ObjectstoCreateList;
      while (ListLen(LoopObjectList))
      {
         // Get the last object name          LoopObjectName = ListLast(LoopObjectList);
         // Call setterandmixinInject() to inject any setter or mixin dependencies          variables.Singleton[LoopObjectName] = variables.setterandMixinInject(LoopObjectName,variables.Singleton[LoopObjectName]);
         // Remove that object name from the list          ListLength = ListLen(LoopObjectList);
         LoopObjectList = ListDeleteAt(LoopObjectList,ListLength);
      };
      
      // Finally for the requested object, do any setter and mixin injections required       ReturnObject = variables.setterandMixinInject(arguments.ObjectName,ReturnObject);

   </cfscript>
   <cfreturn ReturnObject>
</cffunction>

getDependentObjectList()
The first thing getObject() does is to get a list of all of the dependent constructor objects n-levels down by calling getDependentObjectList(). getDependentObjectList() just takes the name of an object (singleton or transient) and looks to see if it has any constructor dependencies, recursively. So, if you passed in “UserService“, it might return UserDAO, DataSource which means that UserService requires UserDAO and that in turn required Datasource.

<cffunction name="getDependentObjectList" returntype="string" access="private" output="false" hint="I return a comma delimited list of all of the dependencies that have not been created yet for an object - n-levels down.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to get the dependencies for.">
   <cfscript>
      var ObjectDependencyList = "";
      var TempObjectDependencyList = "";
      var ObjectstoCreateList = "";
      var LoopObjectName = "";
      var LoopObjectDependencySet = "";
      var CircularDependency = "";
      var ListLength = "";
      var NewObjectName = "";
      var Position = "";
      var KeyType = "ConstructorDependencies";
      If (structKeyExists(variables.Bean[arguments.ObjectName], KeyType))
         {ObjectDependencyList = variables.Bean[arguments.ObjectName][KeyType];}

      // Add the original object name to each element in the object dependency list for circular dependency checking       For (Count = 1; Count lte listlen(ObjectDependencyList); Count = Count + 1)
      {
         // Get current object name          LoopObjectName = ListGetAt(ObjectDependencyList, Count);
         // Prepend it with ObjectName          LoopObjectName = ListAppend(arguments.ObjectName,LoopObjectName,"|");
         // Add it to the new object dependency list          TempObjectDependencyList = ListAppend(TempObjectDependencyList,LoopObjectName);
      };
      // Replace the original object dependency list with the one prepended with its dependency parent for circular dependency resolution checking       ObjectDependencyList = TempObjectDependencyList;
                  
      while (ListLen(ObjectDependencyList))
      {
         // Get the first object dependency set on the list          LoopObjectDependencySet = ListFirst(ObjectDependencyList);
         // Get the list of the object name within that dependency set          LoopObjectName = ListLast(LoopObjectDependencySet,"|");
         // Remove that last record from the list          ListLength = ListLen(LoopObjectDependencySet,"|");
         LoopObjectDependencySet = ListDeleteAt(LoopObjectDependencySet,ListLength,"|");
         
         If (not StructKeyExists(variables.Singleton,LoopObjectName))
         {
            // This object doesn't exist             // Firstly make sure the dependency != circular             If (ListFindNoCase(LoopObjectName,LoopObjectDependencySet,"|"))
            {
               CircularDependency = ListAppend(CircularDependency,"#LoopObjectName# is dependent on a parent. Its dependency path is #LoopObjectDependencySet#");
            }
            Else
            {
               // If it already exists on the list of objects to create remove it from where it is                while (ListFindNoCase(ObjectstoCreateList,LoopObjectName))
               {
                  Position = ListFindNoCase(ObjectstoCreateList,LoopObjectName);
                  ObjectstoCreateList = ListDeleteAt(ObjectstoCreateList,Position);
               };
               
               // Add it to the list of dependent objects to create                ObjectstoCreateList = ListAppend(ObjectstoCreateList,LoopObjectName);         

               // And we need to add its dependencies to this list if it has any                If (StructKeyExists(variables.Bean[LoopObjectName], KeyType))
               {
                  // Set the parent dependency set for this object                   LoopObjectDependencySet = ListAppend(LoopObjectDependencySet,LoopObjectName,"|");
                  
                  For (Count = 1; Count lte listlen(variables.Bean[LoopObjectName][KeyType]); Count = Count + 1)
                  {
                     // Get current object name                      NewObjectName = ListGetAt(variables.Bean[LoopObjectName][KeyType], Count);
                     // Firstly make sure the new dependency != circular                      If (ListFindNoCase(LoopObjectDependencySet,NewObjectName,"|"))
                     {
                        CircularDependency = ListAppend(CircularDependency,"#NewObjectName# is dependent on a parent. Its dependency path is #LoopObjectDependencySet#");
                     }
                     Else
                     {
                        // Append new object with parent dependency to object dependency list                         ObjectDependencyList = ListAppend(ObjectDependencyList,LoopObjectDependencySet & "|" & NewObjectName);         
                     };
                  };
               };
            };
         };
         // Remove the current object name from the list          ObjectDependencyList = ListDeleteAt(ObjectDependencyList,1);
      };
   </cfscript>   
   <cfreturn ObjectstoCreateList>
</cffunction>

Basically, getDependentObjectList() just gets a list of all of the dependencies for the requested object that don’t already exist (if they’re already created, they don’t need to be re-created). It then gets a list of all of the dependencies for any dependent objects recursively until there are no dependencies left.

Please note that sometimes a dependency might come up more than once. For instance, if UserService depends on UserDAO and UserGateway and both depend on DataSource, then assuming none of them have yet been created, getDependentObjectList() would naively return UserDAO, DataSource, UserGateway, DataSource. I have added a while loop to remove any existing references, so the function will actually return UserDAO, UserGateway, DataSource. It is important that it is only the last instance that is left as we will be constructing from last to first, so if both DAO and Gateway need DataSource, it must be constructed before either of them.

Circular Dependencies
getDependentObjectList() needs to test for circular dependencies. In the case of constructor dependencies, it needs to fail if there are any circular dependencies and in the case of setter dependencies, it just needs to be smart enough not to get into an infinite loop.

The only way to find circular dependencies is to keep track of the dependency path for each object and to make sure that you don’t add a dependent object that is on its own parent path. So, if ProductService is dependent on ProductDAO which is dependent on DataSource, it is NOT OK for DataSource to depend on ProductService or ProductDAO. I’m doing this by adding pipe delimited list of dependency sets to the ObjectDependencyList. I don’t love this approach, but it keeps the code fairly simple and will catch any circular dependencies and seems to be working well and catching all of the test cases I can think of.

Creating Objects
The next step is to create all of the objects required, starting at the right hand most end of the list and working back. Each object needs to be created and constructed (by calling init()) before the previous object can be called. All of the creation (including pulling together any constructor properties and objects) is handled by createNewObject() which is called once for each dependent object and then finally once for the object originally requested.

<cffunction name="createNewObject" returntype="any" access="private" output="false" hint="I create a object.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to create.">
   <cfargument name="ObjectType" type="string" required="yes" hint="I am the type of object to create. Singleton or Transient.">
   <cfscript>
      var ReturnObject = "";
      var InitStruct = StructNew();
      var TempObjectName = "";
      var Count = 0;
      var KeyType = "ConstructorDependencies";
      // Get the configured object path       var ObjectPath = "#variables.LightWire.BaseClassPath#.#variables.Bean[arguments.ObjectName].Path#.#arguments.ObjectName#";
      // if the objectPath is empty correct the dot path       ObjectPath = Replace(ObjectPath,"..",".","all");
      // Get the initialization data (if any)       If (StructKeyExists(variables.Bean[arguments.ObjectName], "ConstructorProperties"))
         {InitStruct = variables.Bean[arguments.ObjectName].ConstructorProperties;}
      // Add any constructor dependencies   
      If (StructkeyExists(variables.Bean[arguments.ObjectName], KeyType))
      For (Count = 1; Count lte listlen(variables.Bean[arguments.ObjectName][KeyType]); Count = Count + 1)
      {
         TempObjectName = ListGetAt(variables.Bean[arguments.ObjectName][KeyType], Count);
         InitStruct[TempObjectName] = variables.singleton[TempObjectName];
      };      

      // Create the object and initialize it       ReturnObject = CreateObject("component",ObjectPath).init(ArgumentCollection=InitStruct);

      // Give it the AddObject method to allow for mixin injection       ReturnObject.MixinObject = variables.MixinObject;
   </cfscript>
   <cfreturn ReturnObject>
</cffunction>

Setter and Mixin Injection
Before the objects can be returned, they need to have any non-constructor injections taken care of. As well as the standard setter injection (based on set%ObjectName%() methods), I have also added a mixin injection option. The difference between setter and mixin injections is that setter injection requires a set%objectName%() method whereas mixin injection doesn’t. If you need to do anything more than just pass the dependent object in, you want to use setter injection so the setter method can handle any custom logic. If not, you can just use mixin injection to avoid having to mess up your objects API with setter methods that are really only for pseudo-construction purposes.

<cffunction name="setterandMixinInject" returntype="any" access="private" output="false" hint="I handle.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to inject dependencies into.">
   <cfargument name="Object" type="any" required="yes" hint="I am the ob ject to inject dependencies into.">
   <cfscript>
      var DependentObjectName = "";
      // SETTER DEPENDENCIES       // If there are any setter dependencies       If (StructKeyExists(variables.Bean[arguments.ObjectName],"SetterDependencies"))
      {
         // Inject them all          For (Count = 1; Count lte listlen(variables.Bean[arguments.ObjectName].SetterDependencies); Count = Count + 1)
          {
            // Get current object name             DependentObjectName = ListGetAt(variables.Bean[arguments.ObjectName].SetterDependencies, Count);
            If (DependentObjectName NEQ arguments.ObjectName)
            {
               void = evaluate("arguments.object.set#DependentObjectName#(variables.getSingleton(DependentObjectName))");   
            };
         };
      };   

      // MIXIN DEPENDENCIES       // If there are any mixin dependencies       If (StructKeyExists(variables.Bean[arguments.ObjectName],"MixinDependencies"))
      {
         // Inject them all          For (Count = 1; Count lte listlen(variables.Bean[arguments.ObjectName].MixinDependencies); Count = Count + 1)
          {
            // Get current object name             DependentObjectName = ListGetAt(variables.Bean[arguments.ObjectName].MixinDependencies, Count);
            If (DependentObjectName NEQ arguments.ObjectName)
            {
               arguments.object.MixinObject(DependentObjectName, variables.getSingleton(DependentObjectName));         
            };
         };
      };   
   </cfscript>
   <cfreturn arguments.Object>
</cffunction>

<cffunction name="MixinObject" returntype="void" access="public" output="false" hint="I add the passed object to the variables scope within this object.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to add.">
   <cfargument name="Object" type="any" required="yes" hint="I am the object to add.">
   <cfset variables[ObjectName] = Object>
</cffunction>

Handling Circular Setter and Mixin Dependencies
This is something I actually don't need to track. Because of the way my setter and mixin dependency injections work, they don't get stuck in a loop with circular dependencies, they just work!

Conclusion
So, plenty of testing required, but I feel I've learnt a lot writing LightWire, it only took a few hours and it still weighs in at under 300 lines of code in a single cfc. It also provides a programmatic config file, mixin injections, base class paths and a few other little enhancements that work really well for the way I program. Hey, it's not ColdSpring, but it is still kinda fun!

Comments
*heebie-geebies*

First reactions??

cfinclude in a CFC feels very evil.

Config SHOULD just contain values, not operations... using XML or properties files can't DO anything... your config shouldn't be made of code, they should be made of VALUES that your CODE handles and uses to do it's job. Using code for config... probably won't kill anyone, but I don't like it.

If nothing else, its the sort of thinking that leaves people thinking "well I just want to throw a UDF in my config file, too, because I don't want to extend it..." There's reasons that we structure things the way we do, and I think this idea clouds the idea of the separation of concerns and makes people lazy. Use XML, use a properties file, use an INI file...

But... it's your IoC widget, Peter... so do what you want to do. :)
# Posted By Jared Rypka-Hauer | 11/20/06 3:51 AM
Hi Jared,

Thanks for the comments!!! Interesting re: cfinclude in cfc - I'll have to think about that one a while.

I get what you're saying about config files and separation of concerns and agree that it could be abused. I just find that often configuration values ARE programmatically set (base class path starts with #application.name# or If in development mode use value A else use value B) and the thought of having multiple similar config files just to avoid allowing code in config files is extremely un-DRY.

Good points though. I wonder if this is a static vs. dynamic typing style debate where the power of programmatic config files allows you to code more quickly/tersely but also allows you to make a mess if not careful (like sticking UDFs into config files which is indeed evil)?

Of course, Spring supports programmatic configs, and Fowler wasn't inherently down on them so it's not like this is a non-standard technique. Here are Fowlers thoughts on programmatic configs as part of an essay on IoC:
http://www.martinfowler.com/articles/injection.htm...

Thanks for the input though - will keep thinking about this!
# Posted By Peter Bell | 11/20/06 8:04 AM
I've been thinking... "why do I disklike this idea?" specifically programmatic configs, that is.

I can come up with a few ideas:

#1, validation
DTD/Schema... all gone, bye-bye. One of the beauties of XML, specifically, is:
<cfset variables.config = xmlParse(doc)>

and you're done, yet you can use DTD/Schema to validate, provide insight in an editor, etc., and it's very portable. Hell if you wanted to use attributes only you could just do:

<cfset variables.config = xmlParse(document).xmlRoot.xmlAttributes>

#2: file handling

<cfinclude /> strikes me as an inelegant way to do this... especially considering that the file is read, parsed and executed all in one step. So your choices are a cfinclude error or, well, a cfinclude error. You COULD wrap it in a try/catch block, that still gives you a granularity of, well, one operation. If you do a read/parse/validate/handle routine of your own, you get the separation of code and config that you get with an xml or properties file, but you also get at least 4x the granularity when it comes to detecting problems and throwing clean, understandable error messages. Why on earth would a "missing include file" happen when I'm instantiating a CFC?

As far as using cfinclude in a CFC, I think it's bad practice mostly because it begins to chip away at the fundamental values of OO. If you need to extend a CFC with common code, use inheritance, or build yourself a factory and use composition to provide your CFCs with access to some utility classes... using cfinclude in CFCs is like using cfinclude CFM templates: it's convenient, it's easy, but 90% of the time that it gets done, it's the wrong answer and something else would be far superior. I'm not saying you did the wrong thing here, because your choice is really very clever.

I'm just saying I'd have done it differently and don't care for the technique in general. Just use a config bean, dude... pass your XML file path into your root object, which creates a config bean that it passes xmlParse(arguments.configPath) into. Then in your config bean, return the values directly from the XML. You can do lovely things in XML, like this:

<config mode="development">
<propty name="1" mode="development">value1</propty>
<propty name="1" mode="production">value2</propty>
</config>

Or beter yet:

<config mode="dev">
<dsn forMode="development">someFooDsnName</dsn>
<dsn forMode="production">someProdDsnNameInstead</dsn>
</config>

I guess it's the incredible syntactic and semantic powers of XML that I love... you can describe something in ways that makes perfect sense, and then in code say:

<cfif xml.xmlRoot.xmlAttribtes.mode is "development"> ... more code

It's just as flexible, but more descriptive, truly separates the values from the code that handles them, and yet doesn't require much extra work on the part of the developer (and in many ways makes for less work).

I see what you're saying, but I don't thing it's as simple as "tomato vs tomahto"... I really do think that some things aren't the greatest of ideas. When you're editing a config file, you shouldn't need to touch code... that's my mantra and I'm sticking to it.

Laterz
J
# Posted By Jared Rypka-Hauer | 11/20/06 11:52 AM
Hey Jared,

Wow - MANY thanks for the comment - very detailed. I'm locked today, but I'm gonna take time and really think this through. The DTD validation benefits of XML are a no-brainer and something I'd already factored in, but there are a number of other issues you raised which are really valid.

I'll do some real thinking about this. If nothing else, I really need to write an XML configured framework just to give it a fair try and to get used to using XML in CF (I haven't had cause to play with this much).

I'm not going to say that I agree completely (yet), but there are a bunch of points here that deserve serious consideration. Thanks for taking the time to post this and I'll definitely take the time to think this through. I think at the very least I'll add the option of XML config so I've written the code and know what I'm talking about and then I'll see from there. Thanks!
# Posted By Peter Bell | 11/20/06 12:07 PM
Hey Jared,

So, I've been thinking about this a bit as you'll see from my last few posting. Breakthrough to me was to distinguish the fact that an XML config file is really about separation of concerns and that you may well still use a configuration script for the dynamic (expandpath and cgi.servername) kind of configurations. I'd also say the benefits are for frameworks, projects you'll hand over to maintain and projects with multiple developers - a simple programmatic config does no harm if you're just writing code that only you will be maintaining - especially for small/simple projects.

Have posted latest thoughts:
http://www.pbell.com/index.cfm/2006/11/26/Configur...

Would you agree that's going in the right direction or anything you still disagree with?!
# Posted By Peter Bell | 11/26/06 4:16 PM
Jared, I'll take a little issue with what you said here, though it is a bit off -topic:

<quote>
As far as using cfinclude in a CFC, I think it's bad practice mostly because it begins to chip away at the fundamental values of OO. If you need to extend a CFC with common code, use inheritance, or build yourself a factory and use composition to provide your CFCs with access to some utility classes... using cfinclude in CFCs is like using cfinclude CFM templates: it's convenient, it's easy, but 90% of the time that it gets done, it's the wrong answer and something else would be far superior. I'm not saying you did the wrong thing here, because your choice is really very clever.
</quote>

I don't think using cfinclude in a CFC has that effect. I can see how it could, but I prefer to think of it as a mixin. Sure, I can use inheritance - but what if I want to inherit from something more closely related? That leaves composition, but then I would need to clutter my component with additional interface to acheive the same effect. Using cfinclude as a mixin, I get all the benefits of code-reuse, without the hassle.
# Posted By Sam | 11/30/06 10:25 AM
Hi Sam,

Firstly I think you need to distinguish between what I did and a class based mixin. With a class based mixin at least you are mixing in well formed functions using a cfinclude, not just a little bit of script.

Also, I don't love class based mixins. They work and have a place, but they do make it hard to tell what code is in a given function. I think they are a great "quick hack" and a great "last resort" and I love using them personally, but it is a pattern that if overused could make your code harder for others to figure out.
# Posted By Peter Bell | 11/30/06 11:49 AM
Peter,

Only after I posted that had I realized you simply included some script within a function. I certainly see a difference between the two.

Of course, if you abuse any power it can come back to haunt you, but I love the idea of mixins. Not that I use them all over the place, but the power and flexibility it gives you is incredible.

In any case, I guess I should see the context before I make a fool of myself, sounding like I'm advocating something which I'm not even aware of =).
# Posted By Sam | 11/30/06 2:50 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.