Thursday, January 7, 2010

Using a ColdSpring Post Processor to add "Static" Domain Classes

This is a quick follow-up to an idea I had created by one of Dan Vega's latest posts.

If your application has Products, you might have a ProductController that looks something like this:


component {

property productService;

public array function list() {
return getProductService().listProducts();
}

}


Then you might have a ProductService that might look like:


component {

property dao;

public array function listProducts() {
return getDAO().list("Product");
}

}


And finally you might have a generic DAO that looks like this:


component {

public array function list(string entityName) {
return entityLoad(entityName);
}

}


And now you've got proper encapsulation and separation of concerns with loosely couple components, which is good. However, you also have several components that merely delegate to other components.

In Grails and Rails, you can access your data through static methods on your domain classes. So rather than having the full stack of components, you could simply call Product.list() inside your controller.

I wanted to do something similar in ColdFusion.

First, I added a DomainClassInjector factory post processor to ColdSpring. It accepts an array of suffixes as well as the path to a generic domain class.


<bean id="domainClassInjector" class="com.utils.DomainClassInjector" factory-post-processor="true">
<property name="suffixes">
<list>
<value>Controller</value>
</list>
</property>
<property name="classPath">
<value>com.utils.DomainClass</value>
</property>
</bean>


Here's the DomainClassInjector.cfc

component accessors="true" {

property suffixes;
property classPath;

public any function init() {

variables.domainClasses = {};
variables.entityNames = ormGetSessionFactory().getAllClassMetaData();

return this;

}

public void function postProcessBeanFactory(required any beanFactory) {

var i = "";

local.beanDefinitions = arguments.beanFactory.getBeanDefinitionList();

for (i=1; i <= arrayLen(variables.suffixes); i++) {

local.suffix = variables.suffixes[i];

local.length = len(local.suffix);

for (local.beanName in local.beanDefinitions) {

if (right(local.beanName, local.length) == local.suffix) {

local.bean = arguments.beanFactory.getBean(local.beanName);

for (local.entityName in variables.entityNames) {

if (structKeyExists(local.bean, "set#local.entityName#")) {

if (structKeyExists(variables.domainClasses, local.entityName)) {
local.domainClass = variables.domainClasses[local.entityName];
}
else {
local.domainClass = createObject("component", variables.classPath);
local.domainClass.setEntityName(local.entityName);
variables.domainClasses[local.entityName] = local.domainClass;
}

evaluate("local.bean.set#local.entityName#(local.domainClass)");

}

}

}

}

}

}

}


And here's the DomainClass.cfc

component accessors="true" {

property entityName;

public any function new() {
return entityNew(getEntityName());
}

public any function load(required string id) {
return entityLoadByPK(getEntityName(), arguments.id);
}

public array function list() {
return entityLoad(getEntityName());
}

public any function onMissingMethod(required string missingMethodName, required struct missingMethodArguments) {
// logic for dynamic finders...
}

}


Now I can inject any domain classes I want into my controllers by adding a property that matches the name of my domain class. Here's what my ProductController looks like now:


component accessors="true" {

property Product;

public array function list() {
return Product.list();
}

}


I'm not sure if I'm ready to abandon the Controller/Service/DAO stack yet, but it's nice to know I have options. If I were to fully embrace this approach, I would probably have my entities all extend the DomainClass.cfc, then have the DomainClassInjector inject new instances of my entities into my controllers.

3 comments:

  1. First off I am with you on the controller/service/dao stack. I never understand the reason for delegation from service to dao in this manner. I think I have found situations where it makes sense but if your controllers are not bloated it seems to me that you can call your persistence layer right from your controller method.

    With that said I like your approach but I am a little confused with your terminology. What you are calling your "DomainClass" looks like the persistence layer. I would make that clear to people because there should be clear separation of the entity definition (domain) and the persistence layer.

    I like the idea though! It would be nice to declare a property product of type propertydao and just use my persistence layer right in my controller.

    Great stuff Tony!

    ReplyDelete
  2. Thanks. As for the terminology: http://martinfowler.com/bliki/TwoHardThings.html

    ReplyDelete