On Thu, Jan 20, 2011 at 18:50, Caleb James DeLisle <[email protected] > wrote:
> > > On 01/20/2011 12:33 PM, Denis Gervalle wrote: > > On Thu, Jan 20, 2011 at 17:15, Caleb James DeLisle < > [email protected] > >> wrote: > > > >> > >> > >> On 01/20/2011 02:42 AM, Denis Gervalle wrote: > >>> On Wed, Jan 19, 2011 at 19:54, Caleb James DeLisle < > >> [email protected] > >>>> wrote: > >>> > >>>> > >>>> > >>>> On 01/19/2011 12:13 PM, Vincent Massol wrote: > >>>>> Hi Caleb, > >>>>> > >>>>> I see you're excited, that's good! :) > >>>>> > >>>>> Some general comments: > >>>>> * This looks more like a design for a transaction module than for a > >>>> persistence engine. I don't see anything related to persistence in > your > >>>> proposal below. Your proposal could work on stuff others than storage, > >>>> right? > >>>> Yes, this proposal only covers the transaction sub-module of the > >>>> persistence engine. The so far > >>>> un-proposed modules include xwiki-store-serialization, > >>>> xwiki-store-filesystem and a legacy > >>>> attachment storage module: xwiki-store-filesystem-attachments. > >>>> > >>>> Anything which requires transactions could use the TransactionRunnable > >>>> although I'm at a loss to > >>>> think of anything other than storage which would require transactions. > >>>> > >>>>> * I was expecting to see some Store/Storage/Persistence interfaces > with > >>>> proposed APIs and explanation on how they could be implemented both > with > >>>> Hibernate and JCR for example. And the relationship with the proposed > >> new > >>>> Model defined. > >>>> I don't like to propose an interface until I have tried to implement > it. > >>>> Also I do not like to > >>>> propose an implementation until I have tried to use it. At this point > >> it's > >>>> far enough off that I > >>>> would rather wait than propose APIs blind. > >>>> > >>>> My experience with attachment store has shown that what we want is a > set > >> of > >>>> functions which provide > >>>> TransactionRunnables to do various things: > >>>> aka: > >>>> TransactionRunnable<T> > getDocumentSaveTransactionRunnable(XWikiDocument > >>>> toSave); > >>>> In a hibernate implementation it would return > >>>> TransactionRunnable<HibernateTransaction> and in a JCR > >>>> it would return TransactionRunnable<JCRTransaction>. > >>>> > >>>> We cannot have APIs like this until TransactionRunnable is agreed upon > >>>> these will return instances > >>>> of it. > >>>> > >>>>> * I was also expecting a strategy defined to migrate users from the > >>>> current implementation of the storage to the new one > >>>> IMO we should change the persistence engine and implement the same > >> schema, > >>>> once the persistence > >>>> engine is rebuilt, then we can consider modifying the schema. The > schema > >> is > >>>> a specification, it may > >>>> not be perfect but it is something to comply with. It is important to > me > >>>> that a we prove that a new > >>>> persistence engine is able comply with existing specifications before > we > >>>> start designing new ones > >>>> around it. > >>>> > >>>>> > >>>>> I noticed some discussions between Denis and you on IRC about all > this. > >>>> Does you latest findings change the proposal below? > >>>> Everything proposed still holds true but I did add 2 new features. > >>>> > >>>> 1. There is a way for a TransactionRunnable<DBTransaction> to be > passed > >> an > >>>> instance of DBTransaction > >>>> using a new method called getContext(). > >>>> > >>>> 2. There is a new class which serves what I believe is an edge use > case. > >>>> Suppose you want to define a TransactionRunnable (we will call it > >>>> YourTransactionRunnable) which > >>>> must run inside of a DBTransaction but it must _also_ run after an > >> instance > >>>> of MyTransactionRunnable. > >>>> You can make MyTransactionRunnable a > >>>> "ProvidingTransactionRunnable<DBTransaction, MyInterface>" and > >>>> then MyTransactionRunnable must run inside of a DBTransaction and we > >> define > >>>> YourTransactionRunnable > >>>> as a TransactionRunnable<MyInterface>. This also allows > >>>> MyTransactionRunnable to share information > >>>> since YourTransactionRunnable.getContext() will provide an > >> implementation > >>>> of MyInterface. Of course > >>>> this feature must be used with care as it provides the tools to write > >>>> horrible constructs but IMO it > >>>> is the type of feature which when you need it, there is no other way > >>>> around. > >>>> > >>> > >>> I just need to add that we have not been able to apply TR and PTR in > >>> particular to a simple store with a JDBC connected database. From our > >>> experience, the PTR cause more issue than it solve and we have to find > a > >>> better way to convey datas between transactions of a given transaction > >>> chain. Data dependencies between transaction could not be easily solved > >> at > >>> compile time since there is many combinatory situation in real life. > >>> > >>> My suggestion would be to provide access to previous transaction using > a > >>> hash of Interfaces exposed by previous transaction. Checking > availability > >> of > >>> needed interfaces could be done by transactions à preRun time which > could > >>> avoid the need of uselessly running the whole chain, if there is a > >>> dependency problem. > >> > >> What you can do is make DBTransaction (the context) extend Map and then > put > >> things in that map. I > >> really don't like that path though since is means that TR3 uses things > >> provided by TR1 and doesn't > >> declare that need. It breaks atomicity by sharing data between > >> transactions, TR3 can't just be > >> plugged in somewhere else where TR1 is not used. It also breaks breaks > >> compile time safety, plugging > >> TR3 in somewhere else (or reordering them) will compile but fail to run. > >> That pattern doesn't really afford you any of the benefits of TR other > than > >> the try, catch safety > >> so, to me, it makes sense in that case for the code for TR1, TR2, and > TR3 > >> to all be inside of the > >> same TransactionRunnable. > >> > >>> > >>> I also doubt that the way of multiple transactions are executed in a > same > >>> single one is correct or useful. Would really prefer to see all > >> transactions > >>> run at a given level before going down the chain. > >> > >> I don't see this as making sense. My goal is to make sure when something > >> goes wrong it fails as > >> early as possible so there is the least amount to rollback. > >> > >> SaveDocumentRunnable > >> | > >> +--SaveObjectRunnable > >> | | > >> | +--SavePropertyRunnable EXCEPTION > >> | +--SavePropertyRunnable <-- Doesn't run. > >> | > >> +--SaveObjectRunnable <-- doesn't run > >> | > >> etc. Doesn't run. > >> > >> I'm interested to know what is the rationale for wanting them to run one > >> level then the next? > >> IE: save document, then all objects then all properties. > >> > >>> This would provide the way > >>> to bundle transaction (and even transaction chain) together by running > >> them > >>> in a single transaction. This would also helps not mixing dependencies > >> since > >>> currently there is an implicit availability of earlier sibling chain of > >>> transactions that does not fit the idea of a context in evolution > checked > >> at > >>> compile time and could provide unchecked implicit dependencies. > >> > >> I still don't quite follow, can you give a real world example? > >> > > > > Well, let me base my real world example on your sample above. > > Imagine that documents are linked to their objects using their primary > key, > > PK which is generated by the underlying database when you records the > > document for the first time, and that we needs creating a new documents > with > > Objects in a single transaction. Your SaveObjectRunnable will need that > PK > > as a FK, how does it get it? Or said in another way, it needs a context > that > > provide the PK of the document for which it is saving the objects. > > If the object knows what document it belongs to couldn't you use: > object.getDocument().getKey() ? > I think allowing the document to know it's primary key is less ugly than > passing everything around > in a map. > For sure, this is a contrive example which I try to use to exposed what I means. It is not perfect, just too simple in fact. > Alternatively you could use: > UPDATE object SET object.foreignKey=(SELECT doc.id where doc.name = > <object.getDoc().getName()>) or > the like. It's harder on the DB but it will make each TR modular and > atomic. > But there is no assurance it will works since you have never say that the document should be created to save its objects. > > > Well, lets insert a CreateDocumentRunnable before it, that may have some > > lower TR to complete, like saving document and retrieving the generated > PK. > > How does this CreateDocumentRunnable is maybe a sibling > > of SaveObjectRunnable provide the PK to it ? > > I don't understand why the SaveObjectRunnable would be a sibling and of the > SaveDocumentRunnable. If > the Object is part of the document then shouldn't it be a child? > No, SaveObjectRunnable will not be sibling of the SaveDocumentRunnable, it will be a child of it, for sure, but it could be a sibling of let say CreateDocumentRunnable which have itself some children for doing its own job. > > How do you check > > that SaveObjectRunnable requirement to have that PK is fulfilled ? Have > you > > an idea of the structure needed for this example ? > > IMO siblings should never depend on one another, if the TR needs something > from the last TR then it > really needs to be a child of it. this also has the benefit that you can > pass around a TR for saving > a document and it will have all TRs for saving each object and each > property inside of it. > To say it an other way, saving a document, could be divided in several pieces, and there is no assurance that you could always convert that in parent child relationships, especially when a third transaction depends on the result of 2 previous one that are not dependent from each other but are bundled together in a third one. That would be a perfect world if you could really expect that all dependencies are one to many, and never many to one. A / \ B C \ / D A is composed of B and C, and D depends on A. Thank you, I really appreciate your review. > Thanks ;), I really hope it could make it better ! Denis > > Caleb > > > > > Denis > > > > > >>> > >>> To conclude, this is a very interesting proposal, that needs more > >>> refinements before being used wildly in all situation requiring > >>> transactional processing. > >> > >> > >>> Nice idea and good job Caleb ! > >> > >> Thanks :) > >> > >> Caleb > >> > >>> > >>> Denis > >>> > >>> > >>> > >>>> > >>>> Caleb > >>>> > >>>>> > >>>>> Thanks > >>>>> -Vincent > >>>>> > >>>>> On Jan 10, 2011, at 2:15 PM, Caleb James DeLisle wrote: > >>>>> > >>>>>> 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 > >>>>> > >>>> > >>>> _______________________________________________ > >>>> devs mailing list > >>>> [email protected] > >>>> http://lists.xwiki.org/mailman/listinfo/devs > >>>> > >>> > >>> > >>> > >> > >> _______________________________________________ > >> devs mailing list > >> [email protected] > >> http://lists.xwiki.org/mailman/listinfo/devs > >> > > > > > > > > _______________________________________________ > devs mailing list > [email protected] > http://lists.xwiki.org/mailman/listinfo/devs > -- Denis Gervalle SOFTEC sa - CEO eGuilde sarl - CTO _______________________________________________ devs mailing list [email protected] http://lists.xwiki.org/mailman/listinfo/devs

