Zend Framework Db Table ORM

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Zend Framework Db Table ORM

zsamer
Zend Framework Db Table ORM is a project that aims to give greater ORM functionality to Zend_Db_Table Relationships. These extend the class "Zend_Db_Table_Abstract" and "Zend_Db_Table_Row_Abstract" without touching or altering anything of its existing features.
Reply | Threaded
Open this post in threaded view
|

Re: Zend Framework Db Table ORM

BillKarwin
zsamer wrote
Zend Framework Db Table ORM is a project that aims to give greater ORM functionality to Zend_Db_Table Relationships.

These extend the class "Zend_Db_Table_Abstract" and "Zend_Db_Table_Row_Abstract" without touching or altering anything of its existing features.
I haven't looked at your ORM functionality yet.  Your SVN link isn't responding and you provide a download only in the godforsaken RAR format.

But I just noticed on your wiki one of your features is to track "transaction level."  You increment a counter on beginTransaction() and decrement on commit() or rollback(), doing an actual commit or rollback only when the "level" is 1.  This has been proposed before, but it doesn't work.

http://framework.zend.com/wiki/display/ZFPROP/Nested+Transaction+Support+for+Zend_Db_Adapter_Abstract+-+Bryce+Lohr

Like it or not, transactions are "global" and they do not obey object-oriented encapsulation.

Problem scenario #1

I call commit(), are my changes committed? If I'm running inside an "inner transaction" they are not. The code that manages the outer transaction could choose to roll back, and my changes would be discarded without my knowledge or control.

For example:

Model A: begin transaction
Model A: execute some changes
Model B: begin transaction (silent no-op)
Model B: execute some changes
Model B: commit (silent no-op)
Model A: rollback (discards both model A changes and model B changes)
Model B: WTF!? What happened to my changes?

Problem scenario #2

An inner transaction rolls back, it could discard legitimate changes made by an outer transaction. When control is returned to the outer code, it believes its transaction is still active and available to be committed. With your patch, they could call commit(), and since the transDepth is now 0, it would silently set transDepth to -1 and return true, after not committing anything.

Problem scenario #3

If I call commit() or rollback() when there is no transaction active, it sets the $_transactionLevel to -1. The next beginTransaction() increments the level to 0, which means the transaction can neither be rolled back nor committed. Subsequent calls to commit() will just decrement the transaction to -1 or further, and you'll never be able to commit until you do another superfluous beginTransaction() to increment the level again.

Basically, trying to manage transactions in application logic without allowing the database to do the bookkeeping is a doomed idea.  If you have a requirement for two models to use explicit transaction control in one application request, then you must open two DB connections, one for each model. Then each model can have its own active transaction, which can be committed or rolled back independently from one another.

Regards,
Bill Karwin
Reply | Threaded
Open this post in threaded view
|

Re: Zend Framework Db Table ORM

zsamer
Hi Bill,

it's strange, for my the svn work fine, i don't have any problem.

Respect to the transactions level, i understand your viewpoint and Problem scenario, but in this specific scenario, the ORM scenario, work fine.

Please, see how propel solve this problem is very similar to my method.

http://propel.phpdb.org/docs/api/1.3/runtime/__filesource/fsource_propel-util__utilPropelPDO.php.html


Bill Karwin wrote
zsamer wrote
Zend Framework Db Table ORM is a project that aims to give greater ORM functionality to Zend_Db_Table Relationships.

These extend the class "Zend_Db_Table_Abstract" and "Zend_Db_Table_Row_Abstract" without touching or altering anything of its existing features.
I haven't looked at your ORM functionality yet.  Your SVN link isn't responding and you provide a download only in the godforsaken RAR format.

But I just noticed on your wiki one of your features is to track "transaction level."  You increment a counter on beginTransaction() and decrement on commit() or rollback(), doing an actual commit or rollback only when the "level" is 1.  This has been proposed before, but it doesn't work.

http://framework.zend.com/wiki/display/ZFPROP/Nested+Transaction+Support+for+Zend_Db_Adapter_Abstract+-+Bryce+Lohr

Like it or not, transactions are "global" and they do not obey object-oriented encapsulation.

Problem scenario #1

I call commit(), are my changes committed? If I'm running inside an "inner transaction" they are not. The code that manages the outer transaction could choose to roll back, and my changes would be discarded without my knowledge or control.

For example:

Model A: begin transaction
Model A: execute some changes
Model B: begin transaction (silent no-op)
Model B: execute some changes
Model B: commit (silent no-op)
Model A: rollback (discards both model A changes and model B changes)
Model B: WTF!? What happened to my changes?

Problem scenario #2

An inner transaction rolls back, it could discard legitimate changes made by an outer transaction. When control is returned to the outer code, it believes its transaction is still active and available to be committed. With your patch, they could call commit(), and since the transDepth is now 0, it would silently set transDepth to -1 and return true, after not committing anything.

Problem scenario #3

If I call commit() or rollback() when there is no transaction active, it sets the $_transactionLevel to -1. The next beginTransaction() increments the level to 0, which means the transaction can neither be rolled back nor committed. Subsequent calls to commit() will just decrement the transaction to -1 or further, and you'll never be able to commit until you do another superfluous beginTransaction() to increment the level again.

Basically, trying to manage transactions in application logic without allowing the database to do the bookkeeping is a doomed idea.  If you have a requirement for two models to use explicit transaction control in one application request, then you must open two DB connections, one for each model. Then each model can have its own active transaction, which can be committed or rolled back independently from one another.

Regards,
Bill Karwin
Reply | Threaded
Open this post in threaded view
|

Re: Zend Framework Db Table ORM

BillKarwin
zsamer wrote
Respect to the transactions level, i understand your viewpoint and Problem scenario, but in this specific scenario, the ORM scenario, work fine.
The problem from an OO perspective is that you have state (transaction active/inactive) which is global and not under the control of the class that calls transaction methods.  The class looks like it's controlling the transaction state when it calls beginTransaction(), commit(), or rollback(), but it isn't in control, because the behavior of those methods depends on whether another class is also calling transaction methods.  Even worse, a class that uses transactions can't be aware of changes in that global state made by other classes.

This is bad OO design.  It's an example of Common Coupling, in which two modules share some global data or state.

You would be better off using autocommit.  That way there would be no global state for either class to worry about.

zsamer wrote
Please, see how propel solve this problem is very similar to my method.
Hmm.  I see Propel has bollixed its transaction interface too.  I guess I won't be using Propel!

Regards,
Bill Karwin
Reply | Threaded
Open this post in threaded view
|

Re: Zend Framework Db Table ORM

zsamer
Ok, i see.
How can we fix this? What alternative is there for transactions in ORM?

Bill Karwin wrote
zsamer wrote
Respect to the transactions level, i understand your viewpoint and Problem scenario, but in this specific scenario, the ORM scenario, work fine.
The problem from an OO perspective is that you have state (transaction active/inactive) which is global and not under the control of the class that calls transaction methods.  The class looks like it's controlling the transaction state when it calls beginTransaction(), commit(), or rollback(), but it isn't in control, because the behavior of those methods depends on whether another class is also calling transaction methods.  Even worse, a class that uses transactions can't be aware of changes in that global state made by other classes.

This is bad OO design.  It's an example of Common Coupling, in which two modules share some global data or state.

You would be better off using autocommit.  That way there would be no global state for either class to worry about.

zsamer wrote
Please, see how propel solve this problem is very similar to my method.
Hmm.  I see Propel has bollixed its transaction interface too.  I guess I won't be using Propel!

Regards,
Bill Karwin
Reply | Threaded
Open this post in threaded view
|

Re: Zend Framework Db Table ORM

BillKarwin
zsamer wrote
Ok, i see.
How can we fix this?  What alternative is there for transactions in ORM?
Model classes do need to use transactions sometimes, because a Model operation may execute multiple SQL statements, and it needs to ensure that the whole logical operation is committed atomically.

But this is complicated because a Model can call another Model.  Which Model gets to begin the transaction and commit it?  Somehow each Model needs to be told whether it has the responsibility to start and finish its transaction, or if the calling code is handling that.

One approach is that no Models may call transaction methods.  Always keep transaction control in the application layer.  That way you have one point of control, and therefore you always know the state.  Unfortunately, the default usage of the database adapter is that everything runs in autocommit mode, and a developer might not create an explicit transaction before calling a Model method that must be wrapped in a transaction.

I designed a Perl module for InterBase, circa 1996.  In my module, a database Connection class was a factory for a Transaction object, and the Transaction was a factory for a SQL Statement object.  That way you could pass the Transaction object to Model classes, and that would provide the context in which to run SQL Statements (note that InterBase permits multiple active transactions per connection, unlike most brands of RDBMS, so the context in which to run a Statement is important).  

Any Model that is passed a Transaction must not finish that Transaction, but instead rely on the calling code to do it.  Any Model that is _not_ passed a Transaction must begin one, pass that Transaction to any other Models it calls, and finish the Transaction after all the work is done.

This is complicated if the calling code may or may not have begun a transaction.  Then the Model has a dilemma:  assuming the calling code has begun a transaction is bad because if it hasn't then we're in autocommit mode, and the Model's work may be done in a non-atomic fashion.  Assuming the calling code has _not_ begun a transaction is bad because if it has, then any transaction control the Model attempts is either a no-op or else gives an error.

So the solution is that the Model must know whether there's a transaction in progress, and behave accordingly.  That's what's missing from the solution in Propel and in your library.  The Zend_Db_Adapter class should have a method "isTransactionRunning()" or something like that, instead of maintaining a "level" counter as you have done.  That way the Model can query the current state; this gives it the same information as in my Perl module where Transaction was a distinct class that was passed to a Model.

One further rule is that developers must use the beginTransaction(), commit(), and rollback() methods uniformly when they want explicit transactions.  No fair bypassing them and executing query("COMMIT"), because then the the Adapter class can't track whether there's a transaction in progress or not (AFAIK most database API's don't have a method to check if a transaction is in progress).

Regards,
Bill Karwin
Reply | Threaded
Open this post in threaded view
|

Re: Zend Framework Db Table ORM

BillKarwin

Bill Karwin wrote
So the solution is that the Model must know whether there's a transaction in progress...
By the way, I realize this is still a form of Coupling.  I'd call it Control Coupling, which could be considered to be a shade better than Common Coupling.  It's difficult to remove all coupling in this problem, because transaction state doesn't obey OO encapsulation.  I think the design that you and Propel use is an attempt to reduce coupling, by making the ORM do the "same thing" (i.e. the same transaction API calls) in all cases.  But it doesn't really help.

Another solution that I didn't mention above is that each Model needs its own transaction.  Unfortunately, most RDBMS implementations only support one active transaction per connection, so this isn't possible.  Then you could say that each Model needs its own database connection, but this is too costly.

Ultimately, it's a genuinely hard problem to make an ORM abstraction that can support the full range of database functionality while providing abstraction.  The ORM code becomes so complex in its implementation and its usage that it leads to the question:  why is it that we're writing an ORM?  Wouldn't it be more efficient to just code SQL in our Model class, and control transactions explicitly in the application's calling code?

Regards,
Bill Karwin
Reply | Threaded
Open this post in threaded view
|

Re: Zend Framework Db Table ORM

zsamer
This post was updated on .
bill thanks for your opinion and comments. I will think about this.

Regards,

Andrés Guzmán

Bill Karwin wrote
Bill Karwin wrote
So the solution is that the Model must know whether there's a transaction in progress...
By the way, I realize this is still a form of Coupling.  I'd call it Control Coupling, which could be considered to be a shade better than Common Coupling.  It's difficult to remove all coupling in this problem, because transaction state doesn't obey OO encapsulation.  I think the design that you and Propel use is an attempt to reduce coupling, by making the ORM do the "same thing" (i.e. the same transaction API calls) in all cases.  But it doesn't really help.

Another solution that I didn't mention above is that each Model needs its own transaction.  Unfortunately, most RDBMS implementations only support one active transaction per connection, so this isn't possible.  Then you could say that each Model needs its own database connection, but this is too costly.

Ultimately, it's a genuinely hard problem to make an ORM abstraction that can support the full range of database functionality while providing abstraction.  The ORM code becomes so complex in its implementation and its usage that it leads to the question:  why is it that we're writing an ORM?  Wouldn't it be more efficient to just code SQL in our Model class, and control transactions explicitly in the application's calling code?

Regards,
Bill Karwin