Introducing Metro - A Transfer ORM Service Factory and More...

2008 December 07
by Paul Marcotte
Metro is a library of components to support rapid development of applications that use ColdSpring and Transfer ORM.

Impetus

While developing projects with Transfer over the course of the past year, I've noticed a repetition in the application model code that led me to adopt some basic conventions thereby actively generating much of the code I would have created with passive code generation. Convention based code generation will only get you so far, there are nuances to an application model that are difficult to achieve with code generation. To hedge this, Metro makes it easy to define concrete components (services or gateways) that extend the core Metro components, so one can write custom code to override and/or augment the actively generated code.

Conventions

The over-reaching convention for metro is the concept of packaging related object so that they are managed by a single service. As an example, Metro includes a security package with simple User, Role and Permission objects that are managed by a single SecurityService composed with a gateway for each object. To achieve this, Metro parses the transfer XML configuration into a simplified config structure.

API

A service created by the Metro ServiceFactory provides get(), new(), list(), save() and delete() methods for objects under a given package. For example, the security package included in metro contains three objects: User, Role and Permission. One can use get("objectName") or get{objectName}(), etc. for each object managed by the service. The syntactic sugar of get{objectName}() is achieved through onMissingMethod.

Setup

Metro is setup using ColdSpring and Transfer. Below is a sample bean definitions configuration included in the tests folder. <beans>

<bean id="TransferConfig" class="transfer.com.config.Configuration">
<constructor-arg name="datasourcePath"><value>${datasourcePath}</value></constructor-arg>
<constructor-arg name="configPath"><value>${transferConfigPath}</value></constructor-arg>
<constructor-arg name="definitionPath"><value>${definitionsPath}</value></constructor-arg>
</bean>

<bean id="TransferFactory" class="transfer.TransferFactory">
<constructor-arg name="configuration"><ref bean="transferConfig"></ref></constructor-arg>
</bean>

<bean id="datasource" factory-bean="TransferFactory" factory-method="getDatasource" />

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

<bean id="transaction" factory-bean="TransferFactory" factory-method="getTransaction" />

<bean id="TransientFactory" class="metro.factory.TransientFactory" singleton="true">
<constructor-arg name="classes">
<map>
<entry key="Result">
<value>metro.util.Result</value>
</entry>
<entry key="Timer">
<value>metro.util.Timer</value>
</entry>
</map>
</constructor-arg>
<constructor-arg name="afterCreateMethod">
<value>setup</value>
</constructor-arg>
<property name="beanInjector">
<ref bean="beanInjector" />
</property>
</bean>

<bean id="ServiceFactory" class="metro.factory.ServiceFactory" lazy-init="false">
<constructor-arg name="TransferFactory">
<ref bean="TransferFactory" />
</constructor-arg>
<constructor-arg name="TransferConfig">
<ref bean="TransferConfig" />
</constructor-arg>
<constructor-arg name="TransientFactory">
<ref bean="TransientFactory" />
</constructor-arg>
<constructor-arg name="componentPath">
<value>model</value>
</constructor-arg>
</bean>

<bean id="SecurityService" factory-bean="ServiceFactory" factory-method="getService">
<constructor-arg name="packageName"><value>security</value></constructor-arg>
</bean>

<bean id="Validator" class="metro.core.Validator" />

<bean id="beanInjector" class="metro.lib.BeanInjector" />

<bean id="TDOBeanInjectorObserver" class="metro.lib.TDOBeanInjectorObserver" lazy-init="false">
<constructor-arg name="transfer">
<ref bean="transfer" />
</constructor-arg>
<constructor-arg name="debugMode">
<value>true</value>
</constructor-arg>
<property name="beanInjector">
<ref bean="beanInjector" />
</property>
</bean>

</beans>
In this example, I set up Transfer using the configuration object and several other objects that are included in the library. Once configured, one can create a Singleton instance of a service by calling the getService() factory method and passing the package name as a constructor argument. The Metro ServiceFactory accepts 4 constructor arguments, the TransferFactory, TransferConfig, TransientFactory (another factory included in the library), and the component path, which is the relative path to your packaged model components. The ServiceFactory will check if any components exist in the package under the specified component path and will return an instance of the concrete class instead of the core class. Concrete components must extend either "metro.core.Service" or "metro.core.Gateway" in order for the ServiceFactory to understand which component to create. Additionally, Gateways must provide an additional metadata in the form of a component attribute "objectName". <cfcomponent displayname="UserGateway" objectName="User" extends="metro.core.Gateway" output="false"> The security package included in metro provides an example of a concrete Service and Gateway.

Conclusion

Conventions based application development is not for everyone. Even if not used in a final product, Metro can be used to rapidly prototype your application.