Are We Adopting OO For The Right Reasons? (A More General Take On The ORM Frameworks Discussion)

What started off as a blog comment has morphed into this rather long blog entry. I apologize for the length but I wanted to get it all out there in one shot rather than try to break it up.

I mentioned Joe's blog entry on ORM frameworks yesterday, and he seems to have hit a nerve if the comments and blog reactions are an indication. He posted a follow up to clarify what he thinks an ORM needs to handle, and there are more interesting comments piling up there as well.

I did want to post my own slightly different take on all of this. First, Mark Mandel, the creator of Transfer, has weighed in through some blog comments. To be clear, I personally have great respect for Mark and consider him a friend, and I know Joe does as well. No one is singling out Transfer, but as the most popular and actively developed CF ORM out there it's probably inevitable that Transfer acts as something of a proxy for all of the frameworks out there.

Mark works incredibly hard on Transfer, and Joe's comments aren't meant to be an exercise in "look at what Transfer can't do that Hibernate can", because that's a pretty unfair comparison. Hibernate has been around for a very long time and has man-YEARS of effort behind it from a huge team of contributors. Mark is one person, and I applaud him for everything he's been able to do with Transfer. I think Joe is taking a much more general look at the state of CF ORM frameworks, and if anyone thinks that Transfer is actually the target, I again say that's a misperception based on Transfer's widespread use.

That said, it doesn't detract from Joe's overall point, and I think it's always wise to take careful looks at where things are in ColdFusion-land and objectively compare them to what else is going on in the wider development world. With that in mind, I thought I would pose a few questions of my own that have been rattling around in my head (for years in some cases).

The reality is that something like Transfer is about the best option we have in the ColdFusion world for helping speed up database interactions in our applications. Is it perfect? Of course not, and even Mark would be the first to agree. He's got a laundry list of features that he's planning to add to Transfer. But the question isn't really "is it perfect?", it's "is it good enough to use in spite of its limitations?"

I have used Transfer myself on a large number of projects, and have tried to contribute to its growth. While none of the CF ORMs out there handle everything that Hibernate can in terms of schema generation, inheritance, polymorphism, and more, does this matter a great deal? I'm not sure that it does, but there are several issues at play and the answer depends on the way those issues come together.

First, most ColdFusion applications are very database-centric. Many of them do not have the need for complex domain models. They need to take data from forms, display lists of information, allow people to edit data, and help people visualize data through graphs and reports. Most people aren't building massive business intelligence systems or recommendation engines. Oh, some people are, don't get me wrong. But most aren't. And there's nothing wrong with that: data is crucial to any organization.

Mark noted in one of his comments that one can get a lot of the way to a good OO model despite the fact that most CF ORMs use the database schema to drive the structure of the domain model. While there is absolutely an impedance mismatch between relational database structure and object models, I would probably agree with Mark that the most common needs can indeed be represented fairly well in a relational schema. Basic forms of composition can be handled as one to many and many to many relationships. Table columns can often represent object properties. This basic similarity is why Transfer seems to work pretty well for a large number of developers.

However, this leads into the second issue at play: even though we can handle some common situations using a database-driven object model, should we? Here we get into more of a grey area.

On one hand, it seems like even data-centric objects or anemic domain models do gain some benefits from things like encapsulation, inheritance, and polymorphism. I'm not saying everyone that uses a CF ORM ends up with completely data-centric objects, and I wrote my ColdSpring Bean Utilities library to help make this less of an issue by allowing Transfer Objects to have ColdSpring-managed beans autowired into them. But I would have to say that most people building CFC-based models probably do have anemic domain models (whether they use Transfer or not). Despite this, one could make the argument that the benefits of OO techniques are still real even in this situation.

But is that true? Do the benefits of OO techniques make data-centric objects worthwhile? My friend Hal Helms would say no. And he does make a convincing argument. OOP is hard. There is a lot of ancillary machinery that tends to do along with it: resolving dependencies, getting data into and out of the object to load or display them, and increased complexity. Procedural code is very straightforward: start at the top and go to the bottom. OOP introduces layers of indirection. Without careful consideration, attempting to follow the execution path of an OO system can be very difficult. You tend to have more files, and more code in those files. Hal likes to say that with anemic, data-centric objects, one reaps all of the pain and complexity of OO with none of the benefits that behavior-rich objects should offer. And if I stand back and look at what he's saying, part of me wonders if he is right.

OOP is clearly the dominant paradigm in programming. As such, it is probably wise for any developer that plans to continue their career to learn it. But if one entertains the possibility that the widespread data-centric approach to OOP in ColdFusion is a bad idea, are we actually harming ourselves as a result? Put another way: are we learning this wrong?

I don't really have an answer. I can see both sides of this and I feel like they both have merits. I believe it comes down to these two points:

  • I think it is very dangerous if someone is taking this anemic, data-centric approach under the illusion that they are really learning OOP. It can be hard to "unlearn" something once it is ingrained, and this situation could very well end up a detriment later on.
  • However, if you are aware that you are using data-centric objects and are doing this with a deliberate choice, that changes things. While the debate about whether the overhead of OO is worth the effort for data-centric objects is still there, at least one is weighing the pros and cons with full awareness of the situation.

The question we all may have to ask ourselves is "Which one are we"? Answering it isn't easy. I'm sure everyone would say they are in the second group, and I'm sure many people are. One may simply have a data-centric application, as many developers do, but may still believe that OO principles like encapsulation, coupled with the range of tools available like Transfer and ColdSpring, are worth the effort and complexity. In fact, I'd wager that most folks who will read this blog entry or frequent the Transfer, CFC-DEV, or ColdSpring lists, are probably doing exactly that. I hope that many may even use my ColdSpring utilities library or other tools to give their objects richer behavior that goes beyond just storing data as properties.

The bigger worry to me is the first group, the group who may be taking this anemic approach under the illusion that they are actually doing and learning real OOP. I don't want to be the guy who tries to wag his finger at someone who is doing something that works for them. But since it seems clear that there are a large number of people who fall into that first category, I have to wonder if there is anything we as a community can do to try and move them into the second group. While the debate about whether the perceived benefits of OO apply even to a more data-centric domain model remains open, I feel like it's important that people be making that choice for themselves, deliberately, with a full understanding of what they're doing. Since I consider myself a proponent of object-oriented programming, I'd hate to think that the current trend to adopt OOP in the ColdFusion world may actually be causing more harm than good to many people. I'd love to hear thoughts and comments on this.

Comments Comments (13) | del.ico.us del.icio.us | Digg It! Digg It! | Linking Blogs Linking Blogs | 5606 Views

Transfer 1.0 Officially Released

It will probably make the rounds in the CF blogosphere today, but Mark just released version 1.0 of the Transfer ORM framework. Great job, Mark! If anyone has been waiting to kick the tires on Transfer until an official 1.0 release, your waiting is over. Go grab it and give it a whirl.

del.ico.us del.icio.us | Digg It! Digg It! | Linking Blogs Linking Blogs | 4104 Views

Slides and Code from My Code Generation Presentation

As promised, here are the slides and code from my cf.Objective() 2008 presentation, "Leveraging Code Generation to Jumpstart Your Development". Check the bottom of this entry for the enclosure.

So far the conference has been great. I've learned a lot at several sessions, including Andrew Powell's integration presentation on using Spring and Hibernate as the Java model for an app using CF for Controller and View. Chris Scott's ColdSpring presentation got my wheels turning with regard to custom Proxy Factories. And the announcement of the ColdFusion Open Initiative was most welcome, with a public bug base and more organized enhancement request system.

I'll post more after the conference is over, but so far it's been as good as I expected it to be!

Comments Comments (1) | Download Download | del.ico.us del.icio.us | Digg It! Digg It! | Linking Blogs Linking Blogs | 8163 Views

Unit Testing Transfer Decorators with CFCUnit, BeanInjector, and ColdMock

Paul Marcotte asked me to post about how one might unit test a Transfer Decorator. So here you go Paul (and anyone else who might be interested) :-) .

To start with, basic testing of a Transfer Decorator is really no different than testing any other component. First, I set up a ColdSpring file to use for the test. Note that, depending on how many things you need to test and what you're testing, you can use the same ColdSpring file to test numerous things. In other words, you don't necessarily have to have a separate ColdSpring XML file for every test.

<beans>
	
	<bean id="transferFactory" class="transfer.transferFactory">
	   <constructor-arg name="datasourcePath"><value>/tests/transfer/testing/datasource.xml</value></constructor-arg>
	   <constructor-arg name="configPath"><value>/tests/transfer/testing/transfer.xml</value></constructor-arg>
	   <constructor-arg name="definitionPath"><value>/tests/transfer/testing/definitions</value></constructor-arg>
	</bean>
	
	<bean id="datasource" factory-bean="transferFactory" factory-method="getDatasource" />

	<bean id="transfer" factory-bean="transferFactory" factory-method="getTransfer" />
	
</beans>
		

From here, the unit test might look like this (I'm using CFCUnit but the approach in CFUnit would be similar):

<cfcomponent name="TestRegisteredUser" extends="org.cfcunit.framework.TestCase">
	<cffunction name="setUp" access="public" output="false" hint="">
		<cfset var local = StructNew() />
		<cfset local.config = '/tests/transfer/testing/coldspring.xml.cfm' />
		<cfset beanFactory = CreateObject('component', 'coldspring.beans.DefaultXmlBeanFactory').init() />
		<cfset beanFactory.loadBeansFromXmlFile(beanDefinitionFile=local.config) />
		<cfset setRegisteredUser(beanFactory.getBean('transfer').get('user', 2)) />
	</cffunction>
	
	<cffunction name="test_customDecoratorMethod" returntype="void" access="public" output="false" hint="">
		<cfset var local = StructNew() />
		<cfset assertEqualsString("This is a custom method in the Decorator.", getRegisteredUser().customDecoratorMethod(), 'Custom decorator response does not match expected value.') />
	</cffunction>
	
	<cffunction name="getRegisteredUser" access="private" output="false" hint="I return the RegisteredUser.">
		<cfreturn variables.instance.RegisteredUser />
	</cffunction>
		
	<cffunction name="setRegisteredUser" access="private" output="false" hint="I set the RegisteredUser.">
		<cfargument name="RegisteredUser" required="true" hint="RegisteredUser" />
		<cfset variables.instance.RegisteredUser = arguments.RegisteredUser />
	</cffunction>
	
</cfcomponent>
		

As you can see, you grab the Decorator from Transfer, and then are free to test the methods in the Decorator the same way you would test any other CFC method.

To take this further, you can actually mix a number of the techniques that I've talked about to handle more complex tests. In this example, I'm using the TDOBeanInjectorObserver to wire in a ValidatorFactory to my Decorator. However, to keep the test isolated to the Decorator, I'm using ColdMock to create a mock of the ValidatorFactory and injecting that into the Decorator:

<beans>
	
	<bean id="transferFactory" class="transfer.transferFactory">
	   <constructor-arg name="datasourcePath"><value>/tests/transfer/testing/datasource.xml</value></constructor-arg>
	   <constructor-arg name="configPath"><value>/tests/transfer/testing/transfer.xml</value></constructor-arg>
	   <constructor-arg name="definitionPath"><value>/tests/transfer/testing/definitions</value></constructor-arg>
	</bean>
	
	<bean id="datasource" factory-bean="transferFactory" factory-method="getDatasource" />

	<bean id="transfer" factory-bean="transferFactory" factory-method="getTransfer" />
	
	<bean id="validatorFactory" factory-bean="MockFactory" factory-method="createMock">
		<constructor-arg name="objectToMock">
			<value>tests.transfer.testing.ValidatorFactory</value>
		</constructor-arg>
	</bean>
	<bean id="MockFactory" class="coldmock.MockFactory" />
	
	<bean id="beanInjector" class="common.components.utility.BeanInjector">
		<constructor-arg name="debugMode"><value>false</value></constructor-arg>
		<constructor-arg name="suffixList"><value></value></constructor-arg>
	</bean>
	
	<bean id="TDOBeanInjectorObserver" class="common.components.transfer.TDOBeanInjectorObserver" lazy-init="false">
		<constructor-arg name="transfer">
			<ref bean="transfer" />
		</constructor-arg>
		<property name="beanInjector">
			<ref bean="beanInjector" />
		</property>
	</bean>
	
</beans>
		

This way, I can test any methods in the Decorator that actually rely on the ValidatorFactory by mocking the results from the ValidatorFactory method calls:

<cfcomponent name="TestRegisteredUser" extends="org.cfcunit.framework.TestCase">
	<cffunction name="setUp" access="public" output="false" hint="">
		<cfset var local = StructNew() />
		<cfset local.config = '/tests/transfer/testing/coldspring.xml.cfm' />
		<cfset beanFactory = CreateObject('component', 'coldspring.beans.DefaultXmlBeanFactory').init() />
		<cfset beanFactory.loadBeansFromXmlFile(beanDefinitionFile=local.config) />
		<cfset setRegisteredUser(beanFactory.getBean('transfer').get('user', 2)) />
	</cffunction>
	
	<cffunction name="test_customDecoratorMethodUsingValidator" returntype="void" access="public" output="false" hint="">
		<cfset var local = StructNew() />
		<cfset local.validator = beanFactory.getBean('validatorFactory') />
		<cfset local.validator.returns("This is the MOCKED ValidatorFactory method.") />
		<cfset assertEqualsString("This is the MOCKED ValidatorFactory method.", getRegisteredUser().customDecoratorMethodUsingValidator(), 'Custom decorator using validator response does not match expected value.') />
	</cffunction>
	
	<cffunction name="getRegisteredUser" access="private" output="false" hint="I return the RegisteredUser.">
		<cfreturn variables.instance.RegisteredUser />
	</cffunction>
		
	<cffunction name="setRegisteredUser" access="private" output="false" hint="I set the RegisteredUser.">
		<cfargument name="RegisteredUser" required="true" hint="RegisteredUser" />
		<cfset variables.instance.RegisteredUser = arguments.RegisteredUser />
	</cffunction>
	
</cfcomponent>
		

Just for reference, the Decorator I'm using in these test examples looks like this:

<cfcomponent hint="Registered User" extends="transfer.com.TransferDecorator" output="false">
	
	<cffunction name="customDecoratorMethod" access="public" returntype="string" output="false" hint="">
		<cfreturn "This is a custom method in the Decorator." />		
	</cffunction>
	
	<cffunction name="customDecoratorMethodUsingValidator" access="public" returntype="string" output="false" hint="">
		<cfreturn getValidatorFactory().validatorFactoryMethod() />		
	</cffunction>
	
	<cffunction name="getValidatorFactory" access="public" returntype="any" output="false" hint="I return the ValidatorFactory.">
		<cfreturn variables.instance.validatorFactory />
	</cffunction>
		
	<cffunction name="setValidatorFactory" access="public" returntype="void" output="false" hint="I set the ValidatorFactory.">
		<cfargument name="validatorFactory" type="any" required="true" hint="ValidatorFactory" />
		<cfset variables.instance.validatorFactory = arguments.validatorFactory />
	</cffunction>
	
</cfcomponent>
		

Hopefully that helps clear up how one might go about testing a Transfer Decorator. It also shows how you can use unit testing, ColdMock, and the Transfer Bean Injector together. Please let me know what you think or if you have any questions or comments about how I'm doing this.

Comments Comments (4) | del.ico.us del.icio.us | Digg It! Digg It! | Linking Blogs Linking Blogs | 7721 Views

More ColdSpring Goodness: Updated TDOBeanInjectorObserver and Release of BeanInjector

I've had some good feedback about the TDOBeanInjectorObserver that I released earlier this week. However, I realized that with a small bit of further effort, I could make the idea even more useful. I had put quite a bit of work and thought into handling the dependency injection and the caching of the dependencies for maximum performance. With this all in place and working, it was only a small step to create a generic BeanInjector component that could be used to autowire any component at all, not just Transfer Decorators! So we now have TDOBeanInjectorObserver v1.1 and BeanInjector v1.0, both of which are part of my ColdSpring Utilities RIAForge project.

From the outside, the TDOBeanInjectorObserver has only changed slightly. The only difference is that you now must pass a BeanInjector property to the TDOBeanInjectorObserver in your ColdSpring XML, like this:

<bean id="transferFactory" class="transfer.transferFactory">
		   <constructor-arg name="datasourcePath"><value>/project/config/datasource.xml</value></constructor-arg>
		   <constructor-arg name="configPath"><value>/project/config/transfer.xml</value></constructor-arg>
		   <constructor-arg name="definitionPath"><value>/project/transfer/definitions</value></constructor-arg>
		</bean>
		
		<bean id="transfer" factory-bean="transferFactory" factory-method="getTransfer" />
		
		<bean id="TDOBeanInjectorObserver" class="project.components.TDOBeanInjectorObserver" lazy-init="false">
			<constructor-arg name="transfer">
				<ref bean="transfer" />
			</constructor-arg>
			<property name="beanInjector">
				<ref bean="beanInjector" />
			</property>
		</bean>
		
		<bean id="beanInjector" class="project.components.BeanInjector" />
		

The main difference from the 1.0 version is that you must define a BeanInjector bean, and that you must pass this in as a property to the Observer. The only other change is that if you were using the optional constructor arguments for "suffixList" and "debugMode", these are now specified for the BeanInjector instead of the TDOBeanInjectorObserver.

The real benefit of this doesn't have much effect on the Observer. It is that anyone can now use the BeanInjector to autowire any component. Why is this useful? I'm glad you asked! ;-)

There has always been a performance penalty for managing non-Singleton (aka "transient" or "per-request") beans with ColdSpring. This is because ColdSpring goes through several phases of lookups and dependency resolution when it creates beans. With Singletons, this is never an issue because it only happens once, at application startup. However, the benefits of being able to manage non-Singleton beans with ColdSpring, such as autowiring of dependencies, were still great enough to outweigh the overhead.

Well now we can have the best of both worlds. Using the BeanInjector, you can still have any dependent beans that are managed by ColdSpring autowired into your transient object. But because of the way the BeanInjector caches the dependencies for a given component type, the overhead is virtually eliminated.

A second advantage of using the BeanInjector to autowire your transient objects is that you can maintain control over how your transient object is constructed. When you call getBean() on the ColdSpring BeanFactory, you get back a fully initialized and constructed object. This means that you have very minimal control over how the init() method of your component is called (via constructor arguments in the ColdSpring XML configuration). Using the BeanInjector, you are free to create and call the init() method on your transient objects however you want to, and then autowire them at whatever point in the process you want to.

So for example, consider the following ColdSpring XML:

<bean id="userService" class="components.userService">
			<property name="beanInjector">
				<ref bean="beanInjector" />
			</property>
		</bean>
		
		<bean id="beanInjector" class="components.BeanInjector" />
		
		<bean id="validatorFactory" class="components.ValidatorFactory" />
		

My UserService may want to create and use a transient User object. That User object might have the following function defined in it:

<cffunction name="setValidatorFactory" access="public" returntype="void" output="false" hint="I set the ValidatorFactory.">
			<cfargument name="validatorFactory" type="any" required="true" hint="ValidatorFactory" />
			<cfset variables.instance.validatorFactory = arguments.validatorFactory />
		</cffunction>
		

So now, in my UserService, I can do something like this:

<cfset var user = CreateObject('component', 'components.User').init() />
		<cfset variables.beanInjector.autowire(user) />
		

Which will create the User and then autowire it, so that the ValidatorFactory would be injected into it. So this means you could pass dynamic values to the init() method of user if you want to:

<cfset var user = CreateObject('component', 'components.User').init(someDynamicValues) />
		<cfset variables.beanInjector.autowire(user) />
		

Or even that I could create the object, autowire it, and then call init() in cases where my constructor needs to perform logic that requires the bean to be autowired already:

<cfset var user = CreateObject('component', 'components.User') />
		<cfset variables.beanInjector.autowire(user) />	
		<cfset user.init() />
		

Obviously you have to be careful with this approach since you are not creating and initializing the component in one chained method call, but in many cases where the transient object is local to a function, there is no real chance of the uninitialized object being used by something else before you call init() on it. The point is, the option is there.

In any event, I thought having a BeanInjector like this might be useful to people. Given that most of the work had already been done in the creation of the Transfer Observer, I decided to take the next step and just move that logic into a component that could autowire anything. I'm interested to hear any thoughts on this, so please comment and give your feedback. Thanks.

Comments Comments (2) | del.ico.us del.icio.us | Digg It! Digg It! | Linking Blogs Linking Blogs | 5822 Views

More Entries