Programmatic Configuration and Virtual Beans
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.
<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:
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?


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