How I Use Transfer Today - A Gateway MapFactoryBean
Posted At : November 24, 2008 4:33 PM | Posted By : Bob Silverberg
Related Categories: OO Design, How I Use Transfer, ColdFusion, Coldspring, Transfer
I left something critical out of my last post about how I'm using Transfer (an ORM for ColdFusion) these days.
If you've been following along you'll know that I'm using an Abstract Transfer Decorator, which all of my concrete decorators extend, and that I'm using Brian Kotek's most excellent Bean Injector to load singletons into my Transfer objects. This raises an interesting issue: How to inject the appropriate Gateway Object into my Transfer Object.
Let's start with an overview of how the Bean Injector works. I won't get into the nitty gritty details, but basically it does this:
- Whenever you create a new instance of a Transfer Object, it will check the Transfer Object (i.e., decorator) for any setter method names that correspond to beans in your Coldspring config file.
- If any are found it will invoke the setter, passing the Coldspring bean into it, thereby injecting the bean into your object.
- Optionally, when all of that is done, it will run a user-specified method name. I always use the name setUp(). So, after the bean is injected into my Transfer Object, the setup() method will be invoked, if one exists.
The third item is necessary if you need to do some initial set up of your Transfer Object and that set up requires access to one of the beans that was injected via the Bean Injector. If this isn't entirely clear, I'll be showing a code example of this below, when I show how I implemented my solution.
So, the problem is that you need to have a setter name in your Transfer Object that corresponds to the bean name in your Coldspring config. Therefore, in my User decorator I'd need a setUserGateway() method, whereas in my Product decorator I'd need a setProductGateway() method (because my beans are called UserGateway and ProductGateway, repectively). That isn't such a big deal, but as I'm using an Abstract Decorator I'd like to be able to write one method in the abstract object which could then be inherited by all of my decorators, rather than having to hardcode the setXXXGateway() method into each decorator.
Enter Coldspring's MapFactoryBean! A MapFactoryBean allows you to specify a map in your Coldspring config, which translates into a Struct in your ColdFusion code, and then treat that map/struct as a bean, meaning that you can inject it into other components just as you would a cfc. Here's an example:
2 class="coldspring.beans.factory.config.MapFactoryBean">
3 <property name="sourceMap">
4 <map>
5 <entry key="User">
6 <ref bean="UserGateway" />
7 </entry>
8 <entry key="Product">
9 <ref bean="ProductGateway" />
10 </entry>
11 </map>
12 </property>
13</bean>
If I then put a method in my Abstract Transfer Decorator called setGatewayMap():
2 <cfargument name="GatewayMap" type="any" required="true" />
3 <cfset variables.GatewayMap = arguments.GatewayMap />
4</cffunction>
The Bean Injector will do its magic and I'll end up with a struct in variables.GatewayMap that looks like this:
So now I can create a method called setTheGateway() like this:
2 <cfargument name="GatewayName" type="any" required="true" />
3 <cfset variables.TheGateway =
4 variables.GatewayMap[arguments.GatewayName] />
5</cffunction>
What I'm doing here is passing in a name, and getting back the corresponding Gateway Object from the map. The only issue left is how to specify the name to pass into the setTheGateway() method. There are a number of options for that. For example, you could use a private variable that you define in the configure method of your concrete decorators. Unfortunately that would somewhat defeat the purpose of having these methods in the Abstract Decorator in the first place. What I've done is followed a convention where each package in my transfer.xml file has a corresponding Gateway Object. So, to setTheGateway in my Abstract Decorator I use the setUp() method that was mentioned above:
2 <cfset setTheGateway(ListFirst(getClassName(),".")) />
3</cffunction>
You'll recall that setUp() is run automatically by the Bean Injector after all of the dependencies have been injected into the Transfer Object. I simply call setTheGateway() and pass it the package name of the Transfer Object (using ListFirst()).
And that's it - I now have the appropriate Gateway injected into each of my Transfer Objects without having to add any code into each of my concrete decorators.
I'd like to thank Matt Quackenbush for suggesting the use of the MapFactoryBean for this issue. It's a perfect fit.
My original way to do this was to just define the gateway bean in CS an use a ref to put it in the service like this..
<bean id="TankGateway" class="project.model.tanks.TankGateway">
<property name="TransferClassName"><value>Tank</value></property>
<property name="DescColumn"><value>createdDate</value></property>
</bean>
<bean id="TankService" class="project.model.tanks.TankService">
<property name="TheGateway">
<ref bean="TankGateway" />
</property>
</bean>
I do think I like the GatewayMap idea, but my concerns are:
1) When I am giving the service the MapFactory bean, arent I also giving it all of the gateways besides the single one I need?
2) All of my gateway definitions would now be confined to one bean definition... but, I like to keep my Decorator, Service and Gateway CFC's and Configs in per model (package) folders.. and then include the configs for them on a case by case - app by app basis..
I think I see that by using a MapFactory, this would break my way of separating the service/gateway definitions from the main CS config... not that this is bad by itself, I'm just curious what you think would be the pros vs. cons you can see between the way I have implemented it as exemplified above, or the GatewayMap method described in this post.
Having said that, I will respond to your concerns:
1. When you inject a singleton into an object you are only injecting a reference to the singleton, not the actual singleton object itself. It's true that you are injecting all of the gateways into each decorator (remember, we're talking about decorators here), but I don't think that raises any performance or memory concerns because we are just talking about a few extra references.
2. I can see the benefit to having everything together in one package, particularly if you are including different packages in different apps. In that case I don't know that a simple map will work for you. You may need to look into using a full blown object for this, and then find a way to add the gateways to the object. This is all OTTOMH, I haven't really thought about how to do it, but I'm sure there's an elegant solution to the problem.
I can't really say that one way is better than another. Then issue really is that you cannot do what you've indicated when it comes to Decorators, so you need to come up with a solution that will enable you to do it. Probably the simplest thing would be to just hardcode the setXXXGateway method in the decorator itself, and forget about keeping that part of it abstract.