By Peter Bell

Loading Business Objects

Most of the time I want to call a method on a service class to get a loaded IBO (you know, Product = ProductService.getByID("12") or UserList = UserService.getByFilter("FirstName LIKE 'pet%'")), but for some use cases, some kind of Object.load() would be nice . . .

Lets look at the site user problem I was playing with earlier today. A minor niggle is that my original code was a little longer than it needed to be. I had:

<cffunction name="getSiteUser" returntype="any" hint="I return the correct site user for a given request." output="false">
<cfargument name="SessionToken" type="string" required="false" default="" hint="The token to uniquely identify the site user between requests.">
<cfscript>
var SiteUser = "";
If (Len(SessionFacade.get(SessionToken, "UserID")) GT 0)
{SiteUser = getByID(SessionFacade.get(SessionToken, "UserID"));}
Else
{SiteUser = new();};
// Give the site user the token to access its session scope SiteUser.set("SessionToken", SessionToken); </cfscript>
<cfreturn SiteUser>
</cffunction>

If I had been able to load an existing object I could have tightened that up by writing:

<cffunction name="getSiteUser" returntype="any" hint="I return the correct site user for a given request." output="false">
<cfargument name="SessionToken" type="string" required="false" default="" hint="The token to uniquely identify the site user between requests.">
<cfscript>
var SiteUser = new();
If (Len(SessionFacade.get(SessionToken, "UserID")) GT 0)
{SiteUser.loadByID(SessionFacade.get(SessionToken, "UserID"));};
// Give the site user the token to access its session scope SiteUser.set("SessionToken", SessionToken); </cfscript>
<cfreturn SiteUser>
</cffunction>

Not a big change, but I like it. Honestly though, that isn't enough to add a new method to my API. However, what about this. I have a site user associated to the appropriate session. Now the user wants to log in. I'd really like to have a SiteUser.login(credentials) but under the hood, I'd really need some kind of "If Authenticated, SiteUser.loadByID(Authentication.UserID)" which would add in the appropriate user information into the existing site user without losing the site users session token, cart and other elements.

I think I'm going to come across more use cases for this, so I'm going to add a load method to my IBO that allows for loading by property value and another that allows for loading by filter. Both are just trivial delegates for DAO.getByProperty() and DAO.getByFilter() and I already inject my DAOs into my business objects so it isn't much work for a nice bit of additional flexibility.

Thoughts?

Comments
Peter,

I don't use a SiteUser object, but here's how I've chosen to approach load() when loading an object by Identifier or credentials.

1. Instanstiate the object.
2. Set either Id or credentials.
3. Call Object.load()

Within your object have the load() method check whether the Id is set and call the appropriate delegate method on the DAO. Essentially:

if (structKeyExists(variables.instance,"Id"))
{
variables.dao.read(getInstance());
}
else
{
variables.dao.getByAttributes(argumentCollection=getInstance());
}

Where getInstance() returns the object's varaibles.instance struct.

I use read() and getByAttribute() in place of of your methods getByProperty() and getByFilter(), but the implementation is essentially the same.

So, instead of:

If (Len(SessionFacade.get(SessionToken, "UserID")) GT 0)
{SiteUser.loadByID(SessionFacade.get(SessionToken, "UserID"));};

I would use:

If (Len(SessionFacade.get(SessionToken, "UserID")) GT 0)
{SiteUser.setID(SessionFacade.get(SessionToken, "UserID")
SiteUser.load();};

If your SiteUser object already has setId(), setUername() and setPassword() methods, then all you need to add is a smart load() method rather than loadByID() and login().
# Posted By Paul Marcotte | 5/14/07 2:29 AM
Hi Paul, Thanks for the comments. Hmmm. I've got a feeling that you're doing this the "right" way (or at least the most popular way). I can't remember but some part of me thinks both Reactor and Rails use this kind of approach (although I could be horribly wrong - it has been a LONG time since I've looked at load methods in business objects as on the whole I use a service layer approach for this).

That said, I think I still kind of like what I'm doing - if for no other reason than it makes the methods in the business object really trivial delegates as they expose the same API as the service method.

I think there is probably a good argument that I'm doing this "wrong" and that it'd be a little more OO to take your approach, and yet for now it is working and I am "provisionally happy with it", so I think I'll keep the API and see how it grows on me.

Thanks for giving me something to think about though - really excellent point.
# Posted By Peter Bell | 5/14/07 5:21 AM
Hi Peter,

What makes sense to me for setting the propeties prior to calling load is that in a credentials case, I might decide that validation is required. After SiteUser.setUsername() and SiteUser.setPassword(), I would use:

if (SiteUser.isValidForLogin())
{
SiteUser.load()
}
else
{
[process errors]
}

As long your app works as intended and meets requirements, there's no wrong way of doing things. It's a matter of preference. Forcing oneself into a particular implementation, because it is the norm, is counter-productive. We don't have to "walk in line". :-)
# Posted By Paul Marcotte | 5/14/07 11:14 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.