What I was thinking about the two methods is that the one that creates the transaction could simpy create the transactiona and call the save method that does not have the transaction. I actually mistyped my code, and it would look more like this:

<cffunction name="save" returntype="void" access="public">
   <cftransaction>
      <cfset saveNoTransaction()>
   </cftransaction>
</cffunction>

<cffunction name="saveNoTransaction" returntype="void" access="public">
   <cfset beforeSave()>
   <cfset _getDAO().save(...)>
   <cfset afterSave()>
</cffunction>

I'm not suggesting this is the way to do it, but this is what I meant in my original post.

That said, one thing that would have to be taken into consideration is not just how to roll back the transaction on the database side, but also how to restore the objects to their state before the save() method was called. One place where this should be instantly clear is in the assignment of db-generated IDs to the object that was just created and rolled back in the database.

When the contact is saved, does the new ID get set right away in the ContactRecord? If so, what happens to that value if the transaction is rolled back? Does the ID get cleared out?  What if the address was saved, but one of the phone numbers causes an error? What happens to the contactID in the AddressRecord object that references the ContactRecord? Basically, it seems that every XyzRecord object has to have some sort undoSave() and undoDelete(), and all changes to the objects have to be tracked until the transaction is finally committed. I know that there is a transfer object that exists for each object that holds the last persisted state of the XyzRecord. Perhaps the same sort of mechanism could be built into Record objects.

This all starts to beg the question, is this all worth the trouble? Am I over thinking this, or is there a better solution? Does this problem even exist? I don't want to go too crazy here.

Paul


On 4/25/06, Tormod Boga <[EMAIL PROTECTED]> wrote:
Hi,
 
I just want to say that I have run into the same kind of "issue". Dealing with composite objects, I was not able to enclose my multiple save's in <cftransaction> tags..
I'd really welcome any change to this.
 
Your suggestions look good from my point of view.
 
Regards,
 
Tormod Boga
Inbusiness AS
 


Fra: Doug Hughes [mailto:[EMAIL PROTECTED]]
Sendt: 25. april 2006 13:18
Til: [email protected]
Emne: RE: [Reactor For CF] Transactions...

Paul –

 

First off, it looks like your method names are backwards.  IE save has transactions while vs. saveWithTransaction does not.

 

I do like your suggestion.  I've been thinking about transactions for this very reason. 

 

I see a few options which I will freeform below:

 

1)       Transactions are the developers problem.  In this case I'd remove it from the framework altogether and leave it in the developers court to handle.

 

2)       Transactions are moved into the record and can be optionally disabled.  This is essentially what you've suggested.  I'd do the same thing (I think) for loads and deletes too.  However, rather than having two methods I'd probably have one method that you could pass an argument into.  IE:

 

<cffunction name="save" returntype="void" access="public">

<cfargument name="useTransactions" default="true />

            <cfif arguments.useTransactions>

                  <cftransaction>

                        <cfset beforeSave()>

 

<cfset _getDAO().save(...)>

 

                        <cfset afterSave()>

      </cftransaction>

<cfelse>

                        <cfset beforeSave()>

 

<cfset _getDAO().save(...)>

 

                        <cfset afterSave()>

</cfif>

</cffunction>

 

That said, I'm not sure how that'd word with saves/deletes very cleanly, but we'll see.

 

Any other thoughts?

 

Doug

 


From: [EMAIL PROTECTED] [mailto: [EMAIL PROTECTED]] On Behalf Of Paul Kenney
Sent: Tuesday, April 25, 2006 2:51 AM
To: [email protected]
Subject: [Reactor For CF] Transactions...

 

A while ago, I tried wrapping a call to a XyzRecord's save() method inside a <cftransaction> tag, and discovered that transactions already a part of the framework. After looking at how the transactions are being used, I found that they are placed inside the DAOs in the save() method. A long time ago I learned that a transaction is really a "business-level" concept that encompass all the tables/records needed to persist a single object and all its dependencies. With that in mind, it makes more sense for me to have transactions live in the XyzRecord object, outside of the DAOs used by the XyzRecord object.

For instance, consider a Contact object with an address, a couple of phone numbers and two email addresses. As things stand now,  when I save a contact Record object, the Contact is saved, then the address, phone numbers, and email addresses, each with the id for the saved contact object. If save() is called on the address before the phone numbers or emails, and it fails (from a constraint violation), those phone numbers and emails are not saved. What is saved, though, is a contact record and if any changes were made to its dependent objects the database is now in a state that is inconsistent with the in-memory model.  This is caused by the fact that each object is saved in its own transaction, independent of the others, and the failure of one part (like the address) does not rollback the incomplete persistence of the contact.

What I was thinking might be possible is to have the Record object's save() method operate in two modes: one that creates a new transaction and another that works within an existing transaction. To accomodate these two modes, there would be two methods: save() and saveWithTransaction(). The save method would create a new transaction, and any dependent objects would be saved  using the saveWithTransaction() method.

<cffunction name="save" returntype="void" access="public">
   <cftransaction>
      <cfset beforeSave()>

      <cfset _getDAO().save(...)>

      <cfset afterSave()>
   </cftransaction>
</cffunction>

<cffunction name="saveWithTransaction" returntype="void" access="public">
   <cfset beforeSave()>

   <cfset _getDAO().save(...)>

   <cfset afterSave()>
</cffunction> 

<cffunction name="beforeSave" returntype="void" access="private">
   <cfloop ...>
      <cfset child.saveWithTransaction()>
   </cfloop>
</cffunction>

<cffunction name="afterSave" returntype="void" access="private">
   <cfloop ...>
      <cfset child.saveWithTransaction()>
   </cfloop>
</cffunction>


I'm not sure if this poses any difficulties with the existing way of doing things, or if I have missed something that renders this irrelevant.  Any thoughts, folks?

--
Paul Kenney
[EMAIL PROTECTED]
http://www.pjk.us -- Reactor for ColdFusion Mailing List -- [email protected] -- Archives at http://www.mail-archive.com/reactor%40doughughes.net/



__________ NOD32 1.1505 (20060425) Information __________

This message was checked by NOD32 antivirus system.
http://www.eset.com

-- Reactor for ColdFusion Mailing List -- [email protected] -- Archives at http://www.mail-archive.com/reactor%40doughughes.net/ -- Reactor for ColdFusion Mailing List -- [email protected] -- Archives at http://www.mail-archive.com/reactor%40doughughes.net/



--
Paul Kenney
[EMAIL PROTECTED]
http://www.pjk.us -- Reactor for ColdFusion Mailing List -- [email protected] -- Archives at http://www.mail-archive.com/reactor%40doughughes.net/

Reply via email to