Friday, August 28, 2009

CF9 Front Controller Framework

I want to start writing a very simple front controller framework for CF9 that combines convention simplicity from ColdBox with configuration flexibility from Model-Glue, while borrowing a couple things from Rails/Grails/SpringMVC. Here's what a sample controller might look like:


component extends="Controller" {

public void function listUsers() {

var users = list("User");

render("users/list.cfm",{users=users});

}

public void function showUser() {

var user = get("User",params.userID);

render("users/show.cfm",{user=user});

}

public void function editUser() {

if(exists(flash,"user")) {
var user = flash.user;
}
else {
var user = get("User",params.userID);
}

render("users/edit.cfm",{user=user});

}

public void function saveUser() {

var user = get("User",params.userID);

populate(user,params);

var errors = validate(user);

if(!has(errors)) {

save(user);

flash.message = "User updated successfully";

redirect("showUser",{userID=user.getID()});

}
else {

flash.user = user;

flash.message = errors;

redirect("editUser",{userID=user.getID()});
}

}

public void function deleteUser() {

delete("User",params.userID);

flash.message = "User deleted successfully";

redirect("listUsers");

}

}

Saturday, August 8, 2009

How smart should an object be?

Since I'm relatively new to OOP, one thing I've been struggling with lately is how smart should I make my domain objects. Should an object know how to save itself? Take the following code for example:


user = new User();
user.setFirstName("Tony");
user.setLastName("Nelson");
user.save();


This type of code follows the Active Record design pattern, where domain objects contain your CRUD operations.

Hibernate, which acts as a Data Access Object, takes a slightly different approach. Rather than having the object save itself, Hibernate performs the CRUD.


user = entityNew("User");
user.setFirstName("Tony");
user.setLastName("Nelson");
entitySave(user);


Since Hibernate has become the ORM of choice in ColdFusion, I've decided that an object should not know how to save itself. Now with that in mind, should an object know how to populate itself? Consider the following code:


user = new User();
user.setFirstName(arguments.firstName);
user.setLastName(arguments.lastName);
user.setEmail(arguments.email);
user.setPassword(arguments.password);
user.setBirthDate(arguments.birthDate);
user.setGender(arguments.gender);
user.setHeight(arguments.height);
etc...


Pretty sure we've all seen code like this, where you're just taking all the arguments and calling their corresponding setters by adding "set" in from the argument name. Wouldn't it be a lot simple to just do:


user = entityNew("User");
user.populate(arguments);
entitySave(user);


But again, since a user shouldn't know how to save itself, it probably shouldn't know how to populate itself either, which gives us:


user = entityNew("User");
entityPopulate(user,arguments);
entitySave(user);


Mmmm... entityPopulate() would be a handy little tool. Now what about validation? Same theory applies...


user = entityNew("User");
entityPopulate(user,arguments);
errors = entityValidate(user);
if(ArrayLen(errors) == 0) {
entitySave(user);
}

Well it's short, although I'm not sure it's entirely clear what we're saving since it's so generic. But again, maybe that's a good thing. Ideally, you could get rid of all the "entity" stuff and make a really generic Service that looked like this:


user = new("User");
populate(user,arguments);
errors = validate(user);
if(ArrayLen(errors) == 0) {
save(user);
}


That almost looks as good as pseudo-code, with the only thing that's a little code-y being ArrayLen(errors). Speaking of which, there should be a generic length() function that can accept an array, query, struct, or string and return its length, regardless of data type. Man, that would be pretty sweet. Then the code would look even shorter.


user = new("User");
populate(user,arguments);
errors = validate(user);
if(length(errors) == 0) {
save(user);
}


Seems pretty clean. Just for fun, how would that look using smarter objects and a little method chaining?


user = new("User").populate(arguments);
errors = user.validate();
if(length(errors) == 0) {
user.save();
}


Not too shabby. And now you see my struggles.

Wednesday, August 5, 2009

ColdFusion 9 Wishlist

After playing around with ColdFusion 9 for a little bit, here's my wishlist for future enhancements:

• If you run entityLoadByPK("User",1) and there isn't a User with an ID of 1, it would return an empty User. Either that or add a new method, entityGet("User",1), that would accomplish the same result.

• Add cacheExists(key) that would check to see if a certain key exists in the cache. You can currently use cacheGetAllIds() to return an array of all the keys, then use arrayFind(), but that's a little tedious.

• Allow the ORM event handlers preLoad(), preInsert(), preUpdate(), and preDelete() to return a value or prevent the actual event (load/insert/update/delete) from firing. This would allow you to fetch from the cache before hitting the database a lot easier.

• Add syntax support for dynamic method invocation, similar to user["get#property#"](). You can get around this by using <cfinvoke component="#user#" method="get#property#" />, but it's so verbose.

• Add an implicit "instance" scope to components to keep instance data separate from methods, then update the implicit getters/setters to reference the instance scope. That way, if you wanted the component's instance data (aka memento), you could simply return the instance scope.

• Add support for <cfloop query="#users#" index="user"> where user would be a struct containing the current row's data.

• Add support for <cfloop collection="#users#" index="user"> rather than having to use item="user".

• When looping over a struct, it should loop the keys in alphabetical order. Yeah I know if you want a collection to maintain an order, you should probably use an array, but looping alphabetically is better than looping arbitrarily. This would come in handy when working with API's that use the OAuth protocol.

• Allow you to set the default output value to "false" for all components and methods. Not sure why the default value is "true". Doesn't make much sense.

• Allow you to select a setting to automatically trim all form/url variables. I hate trailing whitespace...

• Maintain the case of keys in structs. Kinda lame how user.name turns into user.NAME but user["name"] stays like it should.

That's all I can think of for now. And although it might appear like I'm complaining a lot, I'm actually really excited about CF9. Lots of good stuff ahead.

Tuesday, August 4, 2009

Handling JavaScript Cookies with Prototype

When dealing with cookies, it's important to be aware of certain browser limitations that might affect your user's experience. For example, Internet Explorer can only handle up to 20 cookies per domain at a time. If your site relies heavily on cookies, this could become a problem if the browser reaches its max and starts tossing cookies unexpectedly. In ColdFusion, this could mean losing your CFID and CFTOKEN cookies and getting logged out for no apparent reason.

To address this, I created a simple Cookie manager with the help of the Prototype library. The basic idea is to store multiple cookies in a single cookie using a JSON key-value pair associative array.

The methods available are:


<script type="text/javascript">
Cookie.get(key, default);
Cookie.set(key, value);
Cookie.clear(key);
Cookie.exists(key);
</script>


For example, I might have the following code to toggle the visibility of a menu:


<script type="text/javascript">
toggleMenu = function(set) {
var visible = Cookie.get('menu', true);
if (set) {
visible = !visible;
Cookie.set('menu', visible);
}
visible ? $('menu').show() : $('menu').hide();
}

Event.observe(window,'load',function(){
toggleMenu(false);
});
</script>


If I were to output the cookie, I would see it's being stored as:


{"menu": true}


If I wanted to add similar functionality to handle the visibility of a sidebar, my code might look like following:


<script type="text/javascript">
toggleItem = function(item, set) {
var visible = Cookie.get(item, true);
if (set) {
visible = !visible;
Cookie.set(item, visible);
}
visible ? $(item).show() : $(item).hide();
}

Event.observe(window, 'load', function(){
toggleItem('menu', false);
toggleItem('sidebar', false);
});
</script>


And my cookies would be stored as:


{"menu": true, "sidebar": false}


Not only is it storing multiple key-value pairs in a single cookie, but we have the added bonus of a really clean API for managing cookies. If you're curious, here's the full Cookie class.


<script type="text/javascript">
var Cookie = {

key: 'cookies',

set: function(key, value) {
var cookies = this.getCookies();
cookies[key] = value;
var src = Object.toJSON(cookies).toString();
this.setCookie(this.key, src);
},

get: function(key){
if (this.exists(key)) {
var cookies = this.getCookies();
return cookies[key];
}
if (arguments.length == 2) {
return arguments[1];
}
return;
},

exists: function(key){
return key in this.getCookies();
},

clear: function(key){
var cookies = this.getCookies();
delete cookies[key];
var src = Object.toJSON(cookies).toString();
this.setCookie(this.key, src);
},

getCookies: function() {
return this.hasCookie(this.key) ? this.getCookie(this.key).evalJSON() : {};
},

hasCookie: function(key) {
return this.getCookie(key) != null;
},

setCookie: function(key,value) {
var expires = new Date();
expires.setTime(expires.getTime()+1000*60*60*24*365)
document.cookie = key+'='+escape(value)+'; expires='+expires+'; path=/';
},

getCookie: function(key) {
var cookie = key+'=';
var array = document.cookie.split(';');
for (var i = 0; i < array.length; i++) {
var c = array[i];
while (c.charAt(0) == ' '){
c = c.substring(1, c.length);
}
if (c.indexOf(cookie) == 0) {
var result = c.substring(cookie.length, c.length);
return unescape(result);
};
}
return null;
}
}
</script>


Enjoy!

Monday, August 3, 2009

ColdFusion 9 ORM Event Handlers

Included with Hibernate is a set of event handlers that can be invoked when loading, inserting, updating, and deleting records from Hibernate. My first thought was to use these event handlers as interceptors to pull data from the cache rather than hitting the database. After further review, it appears as if this isn't possible since all of the event handlers return void. Bummer. On the plus side, you can still use the event handlers to put data into the cache, just not pull it out.

Sunday, August 2, 2009

Hibernate, ColdFusion 9, and DAOs

It's been a long weekend, so this post might seem a little scattered and more-or-less random thoughts. Bear with me.

Since Hibernate gives us a consistent interface for data access, most of the Data Access Objects (DAOs) that we write in CF9 will/should look pretty similar. With that in mind, I've been playing around with creating a generic Data Access Object using simple code generation that could be used and decorated by all persistent objects in the application.

The general idea is that all DAOs are generated by a DAO Factory using a skeleton CFC as a template and basic keyword replacement. The factory would then write the DAOs to disk, where the newly created components would be instantiated and returned to ColdSpring as part of a factory-method.

Here's what my ColdSpring definition looks like:


<beans>

<bean id="userService" class="app.services.UserService" />

<bean id="userDAO" factory-bean="daoFactory" factory-method="getUserDAO" />

<bean id="daoFactory" class="utils.orm.DAOFactory">
<constructor-arg name="relativePath">
<value>/app/proxy/dao</value>
</constructor-arg>
</bean>

</beans>


You can even extend the generated DAO by defining the bean like this:


<bean id="userDAO" factory-bean="daoFactory" factory-method="getUserDAO">
<constructor-arg name="extends">
<value>app.model.user.UserDAO</value>
</constructor-arg>
</bean>



The factory would use the onMissingMethod event handler to determine the correct entity to load by stripping "get" and "DAO" from the factory-method being called. In this case, the entity would be "User". The factory would then do really simple text replacement in the DAO template CFC to generic the UserDAO.

Here's what my DAO template looks like:


<cfcomponent>

<cffunction name="get" access="public" output="false" returntype="any">
<cfargument name="id" required="true" />

<cfreturn EntityLoadByPK("${Entity}",arguments.id) />

</cffunction>

<cffunction name="save" access="public" output="false" returntype="void">
<cfargument name="${entity}" required="true" />

<cfset EntitySave(arguments.${entity}) />

</cffunction>

<cffunction name="delete" access="public" output="false" returntype="void">
<cfargument name="${entity}" required="true" />

<cfset EntityDelete(arguments.${entity}) />

</cffunction>

</cfcomponent>


And here's what the final UserDAO looks like:


<cfcomponent>

<cffunction name="get" access="public" output="false" returntype="any">
<cfargument name="id" required="true" />

<cfreturn EntityLoadByPK("User",arguments.id) />

</cffunction>

<cffunction name="save" access="public" output="false" returntype="void">
<cfargument name="user" required="true" />

<cfset EntitySave(arguments.user) />

</cffunction>

<cffunction name="delete" access="public" output="false" returntype="void">
<cfargument name="user" required="true" />

<cfset EntityDelete(arguments.user) />

</cffunction>

</cfcomponent>


Hopefully that makes some sense. I haven't fully tested everything yet, but the theory is there. The next step would be to update the DAO template to add some more complex logic, like adding validation and leveraging CF9's new caching enhancements. Granted you could always use ColdSpring's AOP to add caching, but that's a different topic.

If anyone is interested in seeing more of the code, let me know.