Trying CFInterface...Not Seeing the Point
Posted on Jan 02, 2008
I am finally getting back to working on my Illudium PU-36 Code Generator which is getting a major overhaul to support things like table and column aliasing among other things. Anyway, in doing so I had what seemed like the perfect use case for interfaces in ColdFusion. Basically, there is a separate component for each supported RDBMS within the generator that each have to implement indentical methods. You can easily add your own connectors by simply implementing all the methods required (and several people have and sent them to me...I have just been really bad about posting a build with them even though they are in SVN). Sounds like exactly what interfaces are made for to me, but the more I try to make this fit, the more I just can't seem to see the point. Let me explain.First of all, as you know, cfinterface is only supported in CF8. Currently the generator supports CF 7.0.2 and up, so I am starting with the premise that implementing interfaces should add some value to my application to warrant limiting the support to only 8. I created an interface along the lines of what follows which represents the "API" of each RDMS specific component:
<cfinterface displayName="rdbms"> <cffunction name="init" access="public" output="false" returntype="mssql"> <cfargument name="dsn" type="string" required="true" /> </cffunction> <cffunction name="setDsn" access="public" output="false" returntype="void"> <cfargument name="dsn" type="string" required="true" /> </cffunction> <cffunction name="getDsn" access="public" output="false" returntype="string"> </cffunction> <cffunction name="getTables" access="public" output="false" returntype="table[]"> </cffunction> <cffunction name="getColumns" access="public" output="false" returntype="column[]"> <cfargument name="table" type="string" required="true" /> </cffunction> <cffunction name="translateCfSqlType" hint="I translate the RDBMS-specific data type names into ColdFusion cf_sql_xyz names" output="false" returntype="string"> <cfargument name="typeName" hint="I am the type name to translate" required="yes" type="string" /> </cffunction> <cffunction name="translateDataType" hint="I translate the RDBMS-specific data type names into ColdFusion data type names" output="false" returntype="string"> <cfargument name="typeName" hint="I am the type name to translate" required="yes" type="string" /> </cffunction> </cfinterface>
It's pretty easy to create and implement the interface and it does serve as a way to clarify the requirements a bit for people creating additional connectors. That's all fine and dandy, but what impact does this have on my application? Currently, the RDBMS-specific component is added via composition to a generic datasource component. I do this so that I only have to define one type of object within Flex for the automatic type translation (i.e. I only have a datasource.as rather than a mysql.as and a mssql.as). The generic datasource component gets and sets the RDBMS-specific type like so:
<cffunction name="setDbms" access="public" output="false" returntype="void"> <cfset variables.dbms = createObject("component","cfcgenerator.com.cf.model.datasource.#dsntype#").init(getDsnName()) /> </cffunction> <cffunction name="getDbms" access="public" output="false" returntype="any"> <cfreturn variables.dbms /> </cffunction>
In this case, the only thing that would change immediately would be the returntype of getDbms() could be set to IRdbms. Other than that, nothing changes. What improvement does this offer? Well, in theory it guarantees that the RDBMS-specific connector has implemented the proper methods required by my code. However, in reality, since there is no compiler and no type-checking it does no such thing. If I implemented the wrong methods in my RDBMS-specific component, ColdFusion will generate a runtime error the same as it would if I didn't use an interface at all and called a method that doesn't exist. The only difference is the type and location of when that runtime error is thrown (a point I know Sean has covered before).
Anyway, I am not saying I have written cfinterface off yet, but I am hoping some of its very vocal supporters might illuminate the point that I may be missing (if I am missing something). Without strongly-typed variables that need to be declared, it just seems like the interface has little to no impact. At the very least, it seems clear to me though that their isn't an obvious "value-add" from using cfinterface to warrant eliminating support for 7.0.2 within the next version of Illudium.
Comments
I have always seen interfaces as a value-add for developers to developers. Its not something your application end user is going to really notice; It helps make rules for expansion in very much so the way you are saying.
Now, one could argue why not just have a document somewhere that says "you must implement the following functions to be considered a valid plugin", but then you have to do all of the heavy lifting to introspect the CFC and make sure all the proper functions are there before you instantiate it. Why not just loosely type an interface and the requirements are then easily checked.
NOW, heres the issue with interfaces in a dynamic language (ie: CF), you can add functions at any time and the interface is only compile-time (I think...), so at runtime you can break the interface and nothing cares.
I don't remember if thats how it goes or not, I am sure someone more qualified than me would chime in if I am wrong.
So in short, depending on your application needs and how nice you want to treat your plugin/extension writers.
Posted By Derek P. / Posted on 01/02/2008 at 12:20 PM
I think how you're setting the Dbms value in your setDbms method is where you're not taking advantage of cfinterface. Usually a set method takes an argument of a specific type and assigns that argument to the class variable.
Your setDbms method doesn't have any arguments. If it did have an argument, you could set the type of the argument to IRdbms and any CFC that implemented that interface type could then be safely sent to the setDbms method.
In the future, as you add new specific types of Rdbms, those types would just need to implement your interface and the code in your setDbms method would not need to be changed. So cfinterface helps you take advantage of polymorphism.
For a more detailed explanation of what advantages I think CFinterface provides see:
http://www.brucephillips.name/blog/index.cfm/2007/8/5/ColdFusion-8-cfinterface-Example--Using-An-Interface-In-CF-8
Posted By Bruce / Posted on 01/02/2008 at 12:31 PM
Yes Derek, that's correct. If you create the object and then modify its methods dynamically you will bypass the interface check. Otherwise it would have to check the interface on every method call, which would be prohibitively expensive.
Basically all the interface provides is a helper construct for other developers. The error and timing is different and subtly superior to just getting a method is undefined error. First, you'd get the error at object creation which helps avoid having an object with an improperly implemented interface getting created and then hanging around for a long time until later when an undefined method error occurs. And the error is more descriptive because it tells you about the interface and you'd easily be able to look to see what the interface specifies instead of guessing or relying on convention.
So, while I would argue that cfinterface is still useful, the usefulness is limited. Probably not worth eliminating CF7 users just to use them. But for internal projects that you know will be on CF8 they may be worth consideration.
Posted By Brian Kotek / Posted on 01/02/2008 at 12:39 PM
What about Railo/BD Compatibility?
Posted By jeff / Posted on 01/02/2008 at 1:45 PM
I'll definitely be interested in hearing how developers react to cfinterface once they actually start using it - as you have done. And, yes, you're right that interfaces bring very little of value to your scenario.
I think interfaces create a brittle type system and pretty much the only situation where I'd use them is as "markers" (interfaces that declare no methods but simply provide a way to 'tag' CFCs as being of particular classes - hard to explain but if you Google 'marker interface' you'll get a lot of more coherent information!).
One issue I have with cfinterface is that it doesn't allow covariant return types or argument type specialization which I think are important concepts (again, go look them up if you care enough :) It also means that if you start using interfaces to specify types in your method arguments, I cannot pass you an object that implements some (or all) of its behavior through onMissingMethod() - so I can't pass an object that satisfies the behavior constraints but instead I am actually forced to write out methods long hand.
And your getDsn() interface has a returntype of void which should be string.
Posted By Sean Corfield / Posted on 01/02/2008 at 2:51 PM
And to answer Jeff's question: cfinterface is different to how BD supports interfaces and I don't believe Railo has interfaces yet (but they would follow CF8 I expect based on past interactions with them).
Posted By Sean Corfield / Posted on 01/02/2008 at 2:52 PM
@Brian - yeah, I guess that is my point, any benefit that may exist is definitely minimal.
@Sean - you bring up another point I considered, being that at some point a specific implementation (say the cfdbinfo version) might choose to meet the requirements but differ slightly in the methods and this allows no variation. Also, thinks for catching the mistake, it's corrected.
@Jeff - BD and Railo are already not supported in that AFAIK they don't support the Flex integration that is necessary so that wasn't a consideration.
Posted By Brian Rinaldi / Posted on 01/02/2008 at 5:11 PM