Hi,
I have been working hard on filesystem attachments and I found that
synchronizing manual filesystem
transactions with automatic database transactions was a difficult job and I
needed a new tool to do
it. So I wrote what I am proposing to be the next XWiki Persistence Engine.
I'll start off with the fun part of the proposal, I have been calling it
xwiki-store so far but I am
so excited about the capabilities of this engine that I don't think it does it
justice to name it
"store" after the place on the corner with milk and eggs. I am proposing it be
named "XWiki
Persistence Engine", the directory will be renamed xwiki-persistence, the
artifact name
xwiki-core-persistence, and the package name org.xwiki.persistence. Persistence
is an attribute of
castles, mountains and redwood trees which I think is fitting for a
conservatively designed storage
engine.
Now a little explanation of what I'm so excited about:
The common and error prone way of saving things in the database is to open a
transaction, enter a
try clause, do something then commit. If we catch an exception, then we
rollback.
something like this:
begin transaction;
try {
do something;
do something else;
commit;
} catch (Any exception which may occur) {
rollback;
}
There are 3 things which can go wrong. 1 we forget to begin the transaction, 2
we forget to commit
and 3 we do not rollback properly. What makes things worse is often the
database will "assume we
meant to..." and things will work ok most of the time which makes things much
worse because bugs
will hide very well.
My answer to this problem is a class called TransactionRunnable. It provides a
set of 5 empty
methods to override: onPreRun(), onRun(), onCommit(), onRollback(), and
onComplete(). the exact
circumstances under which each are called is documented in the javadoc comments
here:
http://svn.xwiki.org/svnroot/xwiki/contrib/sandbox/xwiki-store/xwiki-store-transaction/src/main/java/org/xwiki/store/TransactionRunnable.java
I wrote TransactionRunnable twice, I wrote it, used it for attachments, then
after having real
experience as a user, I wrote it again.
To repeat our original example with TransactionRunnable you might say this:
public class DoSomethingTransactionRunnable extends TransactionRunnable
{
public void onRun()
{
do something;
do something else;
}
}
Now we can use another TransactionRunnable which opens and closes the
transaction for us.
StartableTransactionRunnable transaction = new HibernateTransactionRunnable();
new DoSomethingTransactionRunnable().runIn(transaction);
transaction.start();
the runIn() function allows us to run one TransactionRunnable inside of
another. Supposing we wanted
to reuse "do something else" in other places, we can make it a separate
TransactionRunnable and use
the runIn() function to hook it to our DoSomethingTransactionRunnable ie:
public class DoSomethingTransactionRunnable extends TransactionRunnable
{
public DoSomethingTransactionRunnable()
{
new DoSomethingElseTransactionRunnable().runIn(this);
}
..
The only limitations on running TransactionRunnables inside of one another are
they cannot run more
than once and they cannot call themselves (this would be an infinite loop).
This pattern makes each job which is done on storage easily isolated and, as I
have so far
experienced, trivial to test. However, it still leaves the possibility that we
might forget that
DoSomethingTransactionRunnable must be run inside of a hibernate transaction. I
have devised a
solution for this too. Using generics, I offered a means for the author of a
TransactionRunnable to
communicate to the compiler what other TransactionRunnable their runnable must
be run in and without
explicit casting or defining of an intermediary runnable, this requirement
cannot be violated or
else it wouldn't compile!
Finally we have the issue of starting the runnable. Who's to say I won't be
tired one day and just
write new DoSomethingTransactionRunnable().start() without opening a
transaction first? If
DoSomethingTransactionRunnable cannot be safely run outside of a transaction
all it needs to do is
not extend StartableTransactionRunnable and it won't have any start function.
I have taken a multitude of very easy mistakes and given the author of a
TransactionRunnable the
tools to make it very hard for the user to make them. Also, since a
TransactionRunnable has no
reason to be very long (it can just branch off into another runnable) this will
make testing and
code review easy in the place where it is most important. This part of the code
is entirely generic
and has no dependence on hibernate or anything else.
I propose we move:
contrib/sandbox/xwiki-store/xwiki-store-transaction/
to:
platform/core/xwiki-persistence/xwiki-persistence-transaction
And I will propose moving each additional piece in the coming days.
WDYT?
Caleb
_______________________________________________
devs mailing list
[email protected]
http://lists.xwiki.org/mailman/listinfo/devs