By Peter Bell

Programmatic Configuration and Virtual Beans

Something else I'm going to be presenting today at 2pm EST in terms of the kinds of benefits you can get by using a Dependency Injection engine with a programmatic config file . . .

I make extensive use of base classes and use them as a DSL to allow me to generate most of my methods without having to actually write any code (I can just describe them in metadata). Because of this, many of my beans often have no code. I don't need code inside UserService or ProductDAO or Article (business object) unless they need completely custom methods that can't be described declaritively and I often find I'm launching projects where 80% of my beans have no code.

The only problem is, I don't KNOW which beans have code and which don't so if I want to see how UserService.getByFilter() works, I have to open up UserService, see if there is any code and if not, go to the BaseService to check out the base method there.

Well, I used to have to do this until about a month ago, but then I thought "if a bean doesn't have any code, why not just instantiate the base class and not have the cfc at all?!". Apart from losing the ability to use package security in methods (a non-trivial, but acceptable price to pay), it works just fine. So, then all I needed was a way to tell my DI engine to do the following. "If bean exists in project, instantiate it. If bean doesn't exist in project but exists in shared library, instantiate that. If bean doesn't exist there, just instantiate the base class instead".

Below is some sample code from my last posting that shows you how to do just this for all of the various business beans.

<cfcomponent name="LightBaseBeanConfigBean" extends="lightbase.lightwire.BaseConfigObject" hint="A LightWire configuration bean.">

<cffunction name="init" output="false" returntype="any" hint="I initialize the config bean.">
   <cfscript>
      var BusinessObjectList = "";
      var BusinessObjectName = "";
      Super.init();
      setLazyLoad("false");
      Project = CreateObject("component","lightbase.applications.#Application.name#.config.ApplicationConfig").init();

      // BUSINESS OBJECTS       // controller, service, DAO, IBO and metadata beans       BusinessObjectList = Project.get("ObjectList");
   
      For (ConfigCount = 1; ConfigCount lte listlen(BusinessObjectList); ConfigCount = ConfigCount + 1)
       {
         // Get current object name          BusinessObjectName = ListGetAt(BusinessObjectList, ConfigCount);
      
         // Controller          If (FileExistsinApplication("com.controller.object.#BusinessObjectName#Controller"))
         {addSingleton("lightbase.applications.#Application.Name#.com.controller.object.#BusinessObjectName#Controller");}
         Else
         {
            If (FileExistsinFramework("com.controller.object.#BusinessObjectName#Controller"))
            {addSingleton("lightbase.framework.com.controller.object.#BusinessObjectName#Controller");}
            Else
            {addSingleton("lightbase.framework.com.controller.BaseObjectController", "#BusinessObjectName#Controller");};
         };
         addConstructorDependency("#BusinessObjectName#Controller","#BusinessObjectName#Metadata", "Metadata");
         addMixinDependency("#BusinessObjectName#Controller","#BusinessObjectName#Service");

         // Service          If (FileExistsinApplication("com.model.#BusinessObjectName#.#BusinessObjectName#Service"))
         {addSingleton("lightbase.applications.#Application.Name#.com.model.#BusinessObjectName#.#BusinessObjectName#Service", "#BusinessObjectName#Service", "Config");}
         Else
         {
            If (FileExistsinFramework("com.model.#BusinessObjectName#.#BusinessObjectName#Service"))
            {addSingleton("lightbase.framework.com.model.#BusinessObjectName#.#BusinessObjectName#Service", "#BusinessObjectName#Service", "Config");}
            Else
            {addSingleton("lightbase.framework.com.model.BaseService", "#BusinessObjectName#Service", "Config");};
         };
         addConstructorDependency("#BusinessObjectName#Service","#BusinessObjectName#Metadata", "Metadata");
         addMixinDependency("#BusinessObjectName#Service","#BusinessObjectName#DAO");
         addMixinDependency("LightBase","#BusinessObjectName#Service");
   
         // DAO          If (FileExistsinApplication("com.model.#BusinessObjectName#.#BusinessObjectName#DAO"))
         {addSingleton("lightbase.applications.#Application.Name#.com.model.#BusinessObjectName#.#BusinessObjectName#DAO", "#BusinessObjectName#DAO", "Config");}
         Else
         {
            If (FileExistsinFramework("com.model.#BusinessObjectName#.#BusinessObjectName#DAO"))
            {addSingleton("lightbase.framework.com.model.#BusinessObjectName#.#BusinessObjectName#DAO", "#BusinessObjectName#DAO", "Config");}
            Else
            {addSingleton("lightbase.framework.com.model.BaseDAO", "#BusinessObjectName#DAO", "Config");};
         };
         addConstructorDependency("#BusinessObjectName#DAO","#BusinessObjectName#Metadata", "Metadata");
         addMixinDependency("#BusinessObjectName#DAO","DataMapper");
         addMixinDependency("#BusinessObjectName#DAO","#BusinessObjectName#Service");
      
         // Business Object (IBO)          If (FileExistsinApplication("com.model.#BusinessObjectName#.#BusinessObjectName#"))
         {addSingleton("lightbase.applications.#Application.Name#.com.model.#BusinessObjectName#.#BusinessObjectName#");}
         Else
         {
            If (FileExistsinFramework("com.model.#BusinessObjectName#.#BusinessObjectName#"))
            {addSingleton("lightbase.framework.com.model.#BusinessObjectName#.#BusinessObjectName#");}
            Else
            {addSingleton("lightbase.framework.com.model.BaseBusinessObject", "#BusinessObjectName#");};
         };
         addConstructorDependency("#BusinessObjectName#","#BusinessObjectName#Metadata", "Metadata");
         addMixinDependency("#BusinessObjectName#","DataType");
         addMixinDependency("#BusinessObjectName#","#BusinessObjectName#DAO");
      
         // Metadata          If (FileExistsinApplication("config.#BusinessObjectName#Metadata"))
         {addSingleton("lightbase.applications.#Application.Name#.config.#BusinessObjectName#Metadata", "#BusinessObjectName#Metadata", "Config");}
         Else
         {addSingleton("lightbase.framework.config.#BusinessObjectName#Metadata", "#BusinessObjectName#Metadata", "Config");};
         addConstructorDependency("#BusinessObjectName#Metadata","ApplicationConfig");
         
       };   

      // Mix ALL object services into the Value List Service       For (ConfigCount = 1; ConfigCount lte listlen(BusinessObjectList); ConfigCount = ConfigCount + 1)
       {
         BusinessObjectName = ListGetAt(BusinessObjectList, ConfigCount);
         addMixinDependency("ValueListService","#BusinessObjectName#Service");
       };   

      addMixinDependency("PageService","URIGenerator");

   </cfscript>
   <cfreturn THIS>
</cffunction>

<cffunction name="FileExistsinApplication" returntype="boolean" hint="Whether on not a file exists within the application directory structure" output="false">
   <cfargument name="FileDotPath" required="true" type="string" hint="The file path relative to the application root with dots as directory delimiters.">
   <cfscript>
      var ReturnValue = false;
      var FilePath = variables.ApplicationDirectoryPath & variables.DirectoryDelimiter & Replace(FileDotPath, ".", variables.DirectoryDelimiter, "all") & ".cfc";
      If (FileExists(FilePath))
      {ReturnValue = true;};
   </cfscript>
   <cfreturn ReturnValue>
</cffunction>

<cffunction name="FileExistsinFramework" returntype="boolean" hint="Whether on not a file exists within the framework directory structure" output="false">
   <cfargument name="FileDotPath" required="true" type="string" hint="The file path relative to the application root with dots as directory delimiters.">
   <cfscript>
      var ReturnValue = false;
      var FilePath = variables.LightBaseDirectoryPath & variables.DirectoryDelimiter & "framework" & variables.DirectoryDelimiter & Replace(FileDotPath, ".", variables.DirectoryDelimiter, "all") & ".cfc";
      If (FileExists(FilePath))
      {ReturnValue = true;};
   </cfscript>
   <cfreturn ReturnValue>
</cffunction>

</cfcomponent>

The Key snipper for a particular bean:

// Service
         If (FileExistsinApplication("com.model.#BusinessObjectName#.#BusinessObjectName#Service"))
         {addSingleton("lightbase.applications.#Application.Name#.com.model.#BusinessObjectName#.#BusinessObjectName#Service", "#BusinessObjectName#Service", "Config");}
         Else
         {
            If (FileExistsinFramework("com.model.#BusinessObjectName#.#BusinessObjectName#Service"))
            {addSingleton("lightbase.framework.com.model.#BusinessObjectName#.#BusinessObjectName#Service", "#BusinessObjectName#Service", "Config");}
            Else
            {addSingleton("lightbase.framework.com.model.BaseService", "#BusinessObjectName#Service", "Config");};
         };

Thoughts?

Comments
"if a bean doesn't have any code, why not just instantiate the base class and not have the cfc at all?"

I've been toying around with that idea for some time now. I'll probably end up going that route, but I haven't yet really needed to as most of my stuff sor far contains at least a slight change from the default behavior. Anyway, anything that can be done for me instead of by me is probably going to save me time -- even if it is just creating a blank file with <cfcomponent> in it.

PS: if you keep posting these today, you're going to give away the entire presentation! I'll be trying to attend if I'm free at that time.
# Posted By Sam | 5/16/07 10:31 AM
I find this really works well because I have a set of parameterized base classes (effectively an in language DSL using method calls as a concrete syntax) so the majority of custom functionality I can describe in metadata - not code (which also means I can gen it into n-languages without having to write a language to language transliterator which is may work work than I'm willing to do).

Blank files are easy to gen (I promise to clean up and post my generic cfcgen generator before Scotch at end of month), but a pain to have to look through. No code == no bugs == good life :->

As for the preso, honestly it'll be OK but it'd be a waste of your time. intro to DI, comparison of CS and LW, and then benefits of Programmatic Config, Mixin injection and "ghetto annotations". The ghetto annotations is a very small point, so unless you don't get mixin injection and want to find out more, the rest of the talk will be a bit low level for you. I'd peg it as a solid intermediate to slightly advanced preso and you're a little more on the advanced side.
# Posted By Peter Bell | 5/16/07 11:23 AM
"Blank files are easy to gen (I promise to clean up and post my generic cfcgen generator before Scotch at end of month), but a pain to have to look through. No code == no bugs == good life :->"

I agree with that! At the moment, I'm forcing instantiation of a derivative with code in the base class. I saw the limitations of that and how nice it would be to have classes in non-existant files, but just haven't got around to removing the restriction yet. Lots more in my head, so little time!

As for the presentation, I was thinking about it in several lights not having so much to do with the content, but how to present yourself, topics to cover, level of detail, etc etc, and maybe ribbing you a little. =) I'll try to make it at the time, but I suppose its being recorded and I can get back to it later if need be.
# Posted By Sam | 5/16/07 11:40 AM
Well, always up for someone else ribbing me. As for quality of preso, I'll do what I can but it's a tough week so it may not be up to the quality of my in person presos (whatever that might be!)
# Posted By Peter Bell | 5/16/07 11:50 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.