Transfer ORM and Business Object Factory Library – v0.1

Transfer is a library of CFCs for an idea I had a long time ago, and now I am going to pass it out to you for your feedback, and see if what I have developed is sometime useful that should be further developed.  This is the first time I've ever released anything substantial into the wide space of the Internet, so please bear with me.

The thought for Transfer was that to ultimately cut back on development time, it would be very handy to simply be able to create your database, and a corresponding xml file, and from there your Business Objects could be created, updated and deleted all without the writing of another piece of code as well as being managed in persistant scopes.

This is done through 2 processes –

  1. The business object is manufactured within the library, but unlike a code generator, in which you create your code only once, and the CFCs are defined until you run your code generator again, Transfer works by generating only the udfs that are required by each business object, and decorating a generic TransferObject with the required functions at run time when the particular object is requested.  This allows for easier development as the code generation is completely implicit, you don't need to manually fire it off.
  2. The Transfer Library automatically generates all your SQL for CRUD operations, based upon the details specified in the xml config file.  This includes SQL for composite objects.

To show you how it works, I'll take you through some basic Transfer Object definitions, and then we'll get a little bit more complicated with some composite object creation.

This is taken from the provided tBlog example application that is available for download here.

If we want to have a User in the system, first we will set up a table that has the following details:

Name  Data type
IDUser (PK) numeric
user_Name varchar(500)
user_Email varchar(500)

 

Within our transfer configuration file, we want an object of type 'user.User', and we tell it what table it is from.

<objectDefintions>
   <package name="user">
       <object name="User" table="tbl_User">
          </object>
   </package>
</objectDefintions>

We then define the primary key, which will give us a getIDUser() and setIDUser() on the eventual TransferObject

<objectDefintions>
    <package name="user">
        <object name="User" table="tbl_User">
            <id name="IDUser" column="IDUser" type="numeric"/>
        </object>
    </package>
<objectDefintions>

We then set each of the properties on the 'user.User', in this case, name and email, giving us both get & setName() and get & setEmail() on the user.User TransferObject. Each property has a type, and requires that you specify which column that the property refers to.

<objectDefintions>
    <package name="user">
        <object name="User" table="tbl_User">
            <id name="IDUser" column="IDUser" type="numeric"/>
            <property name="Name" type="string" column="user_Name"/>
            <property name="Email" type="string" column="user_Email"/>
        </object>
    </package>
<objectDefintions>

I won't take you into installing the Transfer library, I'll leave that to the (relatively sparse) documentation and the provided example, but we'll assume that the main class transfer.com.Transfer is available to you.

To retrieve a new empty user.User transfer object, all is required is:

<cfscript>
//get a new user
user = transfer.new("user.User");
</cfscript>

To then create the user as a record in the database, we can now:

<cfscript>
//create the user
user.setName("Mark");
user.setEmail("notanemail@email.com");

transfer.create(user);
</cfscript>

To retrieve a record from the database: 

<cfscript>
//get a user
user = transfer.get("user.User", url.IDUser);
</cfscript>

Update and delete 

<cfscript>
//update a user
user.setName("Fred");
transfer.update(user);

//delete
transfer.delete(user);
</cfscript>

A lot of this is relatively straight forward, however where Transfer really comes into it's own is where it starts handling composite objects.

There are 3 different types of ways in which Transfer can handle different composition of objects, but I will show you one example, so you can get a feel for how it works.

Again, from the tBlog example, we'll look at a Post with Comments (in tBlog, a post has Comments, a User, and multiple Categories, but in this case, we will only worry about Comments).

So first we create a 'post.Post' (I won't show the database table, I figure you have a grasp on how it works), and a 'post.Comment' objects:

<package name="post">
    <object name="Post" table="tbl_Post">
        <id name="IDPost" type="string" column="IDPost"/>
        <property name="Title" type="string" column="post_Title"/>
        <property name="Body" type="string" column="post_Body"/>
        <property name="DateTime" type="date" column="post_DateTime"/>
    </object>
    <object name="Comment" table="tbl_Comment">
        <id name="IDComment" type="numeric" column="IDComment"/>
        <property name="Name" type="string" column="comment_Name"/>
        <property name="Value" type="string" column="comment_Value"/>
        <property name="DateTime" type="date" column="comment_DateTime"/>
    </object>
</package>

This provides us with our basic CRUD operations for the tbl_Comment and tbl_Post, however in the database schema, tbl_Comment has a foreign key, lnkIDPost, that provides a one to many relationship between Post and Comment that we still need to define.

Strangely enough, we do it with an element in <object> called 'onetomany', like so:

<package name="post">
    <object name="Post" table="tbl_Post">
        <id name="IDPost" type="string" column="IDPost"/>
        <property name="Title" type="string" column="post_Title"/>
        <property name="Body" type="string" column="post_Body"/>
        <property name="DateTime" type="date" column="post_DateTime"/>
        <onetomany name="Comment">
            <link to="post.Comment" column="lnkIDPost"/>
            <collection type="array"/>
        </onetomany>
    </object>
    <object name="Comment" table="tbl_Comment">
        <id name="IDComment" type="numeric" column="IDComment"/>
        <property name="Name" type="string" column="comment_Name"/>
        <property name="Value" type="string" column="comment_Value"/>
        <property name="DateTime" type="date" column="comment_DateTime"/>
    </object>
</package>

In this, the name of the onetomany relationship is defined, thus giving us methods like 'addComment()', 'getCommentArray()', 'removeComment()' and a few more on the post.Post TransferObject.  The onetomany element also sets a 'setParentParent()' and 'getParentPost()' method on the Comment itself, for controlling what post the comment is for.

If we want to create a new Comment, and add it to a post, all we need to do now is:

<cfscript>
post = transfer.get("post.Post", url.id);

comment = transfer.new("post.Comment");
comment.setName(form.name);
comment.setValue(form.comment);

comment.setParentPost(post);

//no need to sort, or add to the parent, it is done implicitly.
transfer.create(comment);
</cfscript>

And presto, the comment is created, and has implicitly been added to the Post.

If we were to go through the post.getCommentArray() we would find the new Comment in there. 

You will also note that there is a 'type' attribute of 'array' value on the 'collection' element. This states what sort of collection is going to be created on a post.Post.  The other option is 'struct', in which case you must also set a key to be used.

The advantage of having an array however, means we can control the sorting of the Comments, like so:

    <object name="Post" table="tbl_Post">
        <id name="IDPost" type="string" column="IDPost"/>
        <property name="Title" type="string" column="post_Title"/>
        <property name="Body" type="string" column="post_Body"/>
        <property name="DateTime" type="date" column="post_DateTime"/>
        <onetomany name="Comment">
            <link to="post.Comment" column="lnkIDPost"/>
            <collection type="array">
                <order property="DateTime" order="asc"/>
            </collection>
        </onetomany>
    </object>

This does several things – when a post is first retrieved from the database, all its comments will be sorted by their DateTime of entry, however when a new Comment is added, it is automatically software sorted into the correct order.

Therefore, if during development you have decided that sorting shouldn't be on the DateTime property of a post.Comment, but instead on the Name property, all you would need to do is:

 <order property="Name" order="asc"/>

and it the change ordering would be handled the next time the xml file is read by the library.

There is a lot more to Transfer that I haven't looked at here, but if this has piqued your interest, the following is available for download:

I'll also make available the latest documentation as I write it at:

Which will get updated whenever I have a spare moment. 

If you are going to have a look at what I've written, please do drop me a note, either via the comments below or via my contact form, I would greatly appreciate it.  I've learnt a lot writing this library, so hopefully you can get something out of it as well. 

Please note, this is pretty much Alpha code.  While it does run behind the scenes here, it hasn't had nearly as much testing as is reasonable to run on a mission critical system, so in any way you use it, it is at your own risk.

Leave a Comment

Comments

  • Paul Roe | November 16, 2005

    This does look very nice, it seems to provide a lot of the functionality that arf and reactor are aiming for but I like your use of xml to provide flexibility.

    Quick question, when your setting up your one to many relationship can you define both an array and a structure (maybe even a query too)? This would let you run post.getPostArray() post.getPostStruct() post.getPostQuery() with the following xml:

    <onetomany name="Comment">
    <link to="post.Comment" column="lnkIDPost"/>
    <collection type="array">
    <order property="DateTime" order="asc"/>
    </collection>
    <collection type="array"/>
    <collection type="query"/>
    </onetomany>

    Or perhaps you could just make the creation of the array and the query implicit and only force us to specify structs since that’s the only one that can’t really be done automatically?

  • doug sims | November 16, 2005

    The sample application works swell, however I am stuck trying on my own. I have a transfer.xml file that matches my tables and validates ok, however all i can generate is a error that reads ‘The requested object could not be found in the config file
    Could not find ‘demo.Demo’ defintion"..(demo.demo is my version of user.user)

    My other question is about the gateways in the com folder in the tblog applicaton. are those generated or do you have to code them?

    Any help would be appriciated, I do l long for success after a few hours of errors…!

  • Mark | November 16, 2005

    Paul:
    I think I get at where you are coming from (although I am a bit stuck on how to get an array of objects). I actually can’t see any reason why I couldn’t make it produce an array AND a set of structs… depending on what you ask for… I may look into this and see what comes up.

    Joe:
    Joe, I should mention somewhere that the call to a class is Case Sensitive. It’s the nature of XPath. It’s probably something I should see if I can work around at some point and make case insensitive, but it’s what I have at the moment. So if you are looking for demo.Demo, and you have a package named ‘demo’ and an object inside it called ‘demo’, it won’t find it.

  • Mark | November 16, 2005

    When I said:
    ‘(although I am a bit stuck on how to get an array of objects)’
    I mean to say
    ‘(although I am a bit stuck on how to get a *query* of objects)’

    Figured I would clarify. I’m the worst at proof reading what I write.

  • doug sims | November 16, 2005

    Thanks, I wasn’t being consistant with my case. now i am getting the .transfer files created and am doing reads,creates and updates on single tables. that brings me to my next issue:
    in the tblg demo, are the gateway cfc’s(in the "com" subfolder) autogenerated, or did you have to build them?

  • Mark | November 16, 2005

    My bad, you asked that before, and I didn’t answer.

    Yes, you need to create your own gateways, Transfer ony handles the Business Object side of things.

    I couldn’t think of a way of actually generating gateway style queries in way that would have been faster than writing regular old SQL through Transfer, so it’s wasn’t something I seriously looked at.

    However, if you have any ideas, I’m definatley interested in hearing them.

  • doug sims | November 17, 2005

    This is really neat! and I do appriciate the support.
    I have my gateways set up and everything is working fine except – I can’t seem to add any new fields to the existing tables/transfer.xml file? no matter what type of field i add, i get :

    Element MEMENTO.(fieldname) is undefined in ARGUMENTS.

    where (fieldname) is the name given in the table and used in the transfer.xml. is there a trick to regenerating after adding new fields? It appears the transfer file is being reparsed. should I include my transfer.xml file in case there is an issue in there?

  • Mark | November 17, 2005

    Doug, I’m glad to hear you are enjoying this!

    I’m not quite sure what you are doing here, but some thoughts off the bat:
    I assume you have re-instantiated the TransferFactory when you changed your transfer.xml file? The config file is kept memory resident once it has been read the first time. (I think you have, but I figure I’ll cover all bases).

    Is this error happening in a CFC in the lib, or is it happening in a .transfer file?

    I’ll send you a email off the site, so if you want to send me any files, screenshots or whatever, you are able to.

  • doug sims | November 18, 2005

    Thanks,
    I figured out what the issue was… I didnt want to mess with my existing application.cfc so was was creating an application.transferFactory on my test page. then I did decide to intergate it with my existing application.cfc and forgot and left the call in my test page, somehow this was causing the problems when transfer was regenerating (though now I can’t recreate them by doing this intentionly)

    now I am well past that point and not having any problems.

    I think that the dynamic method creation methodologies in transfer are years beyond anything you would get from the more static code generators that are available.
    Doug

  • Mark | November 18, 2005

    Doug,

    Glad to hear you have got it working, and that you are enjoying it so much.

    I’ve been plugging away at the documentation in my spare time, so please don’t forget to look in there.. and once that’s at some reasonable level, I will create a dedicated page on this site to transfer, and create a mailing list of some kind for issues such as these.

    Thanks again!

  • Elliot Russo | November 23, 2005

    This is all very much like Hibernate and iBatis in Java (I guessing you are influenced by them?). I have actually had some initial success using them from CF. Have looked at doing something similar in pure CF – so interesting to see your approach.

    Thanks for the interesting articles. Keep ’em coming.

    Elliot