|
|
| 1 2 |
|
Aristedes Maniatis
|
Even though the callback system in Cayenne 3 is simple to explain (http://cayenne.apache.org/doc/lifecycle-callbacks.html
) there are some details which can bite. I'd like to create an overview of how I think they currently work and what issues remain: How we use them --------------- * set createdOn attribute in all entities * set modifiedOn attributed in all entities * update calculated denormalised summary fields. For example, invoice lines are added and payments subtracted to create an invoiceOwing attribute. This needs to be updated whenever a relevant record is created or updated. * audit trail records. Every time a change is made to a record, a log entry is created (we actually use this for database replication). * firing special events - in our system when a new payment is created, special credit card processing code runs, all before the ROP client gets a return value from the context.commit(). Now, I understand that lifecycle is tied up tightly with JPA so some of the naming reflects names imposed by JPA. But hopefully Cayenne functionality could go further. Cayenne 2 tier -------------- For new objects : context.newObject() -> prePersist() context.commitChanges() -> postPersist() For existing objects: context.commitChanges() -> preUpdate(), postUpdate() Cayenne 3 tier (ROP) -------------------- For new objects : context.commitChanges() -> prePersist(), postPersist() For existing objects: context.commitChanges() -> preUpdate(), postUpdate() Object state ------------ prePersist() in 2 tier: object attributes and relationships are all null prePersist() in ROP: object attributes populated but relationships always invalid preUpdate(): attributes and relationships are all dependable. In addition, within the context being committed, we can follow a relationship from one object to another and see its new updated attributes. Summary ------- * prePersist() is only useful as a place to set object attributes (such as creationDate) since you cannot follow relations reliably in a ROP environment. * postUpdate() and postPersist() are useful for changes which do not need to be committed atomically with the original commit. So good for creating log records, but not ideal for updating invoiceOwing. * postPersist() is badly named. It is really postInsert() * we need preInsert() Additional callbacks -------------------- preCommit() and postCommit() listeners on the context itself would be really useful. They should run AFTER the per object listeners have all run for that phase. This would be one per context and allow a single entry point which can examine and touch all objects in the context before commit or just after. Additional ROP feature ---------------------- If an object is touched within a pre-commit phase on the server, the ROP client does not get the updated object refreshed automatically. It appears the response the client is either just the validation exception (if there is one) or "all OK". We are currently remembering to set the object on the client to be hollow, but it would be nice for the server to return all committed objects to the client. I hope that I haven't tried to cover too much ground here. My purpose is to get some feedback about the above ideas, and about how hard they are to implement in Cayenne. Since this is a blocking issue for our main commercial project we are want to focus and getting these things clear in the Cayenne docs and making appropriate improvements. Cheers Ari Maniatis --------------------------> ish http://www.ish.com.au Level 1, 30 Wilson Street Newtown 2042 Australia phone +61 2 9550 5001 fax +61 2 9550 4001 GPG fingerprint CBFB 84B4 738D 4E87 5E5C 5EFA EF6A 7D2E 3E49 102A |
||||||||||||||||
|
Aristedes Maniatis
|
On 29/11/2007, at 12:28 PM, Aristedes Maniatis wrote: > Summary > ------- > > * prePersist() is only useful as a place to set object attributes > (such as creationDate) since you cannot follow relations reliably in > a ROP environment. > > * postUpdate() and postPersist() are useful for changes which do not > need to be committed atomically with the original commit. So good > for creating log records, but not ideal for updating invoiceOwing. > > * postPersist() is badly named. It is really postInsert() > > * we need preInsert() With some further reading of the JPA specification I have come to the following conclusions: * the JPA was written by lawyers who get paid by the word * postPersist() is defined in the JPA to occur after a NEW record is written to the database. Which is how Cayenne works. * pre/postUpdate(): the JPA specification is very very unclear, but it looks like maybe it might apply for both new and existing records. But then postUpdate() overlaps postPersist() which is a bit pointless. * if preUpdate() is just for existing records we still need a preInsert(). So, even though we have to follow the poorly named callbacks in JPA, an extra callback would fill a useful hole. Ari --------------------------> ish http://www.ish.com.au Level 1, 30 Wilson Street Newtown 2042 Australia phone +61 2 9550 5001 fax +61 2 9550 4001 GPG fingerprint CBFB 84B4 738D 4E87 5E5C 5EFA EF6A 7D2E 3E49 102A |
|
Andrus Adamchik
|
On Nov 29, 2007, at 10:56 AM, Aristedes Maniatis wrote: >> >> * prePersist() is only useful as a place to set object attributes >> (such as creationDate) since you cannot follow relations reliably >> in a ROP environment. True, and a workaround would be to support relationships for transient objects. Something Kevin was bringing up on multiple occasions. >> * postUpdate() and postPersist() are useful for changes which do >> not need to be committed atomically with the original commit. So >> good for creating log records, but not ideal for updating >> invoiceOwing. Another way to go about atomic commits is to set up a manual transaction that spans a request scope. I think that's an appropriate solution. >> * postPersist() is badly named. It is really postInsert() >> * we need preInsert() per JPA "persist" is "insert", so the naming is correct if somewhat confusing. > With some further reading of the JPA specification I have come to > the following conclusions: > > * the JPA was written by lawyers who get paid by the word It is confusing. Tell me about it! But partially because of differences in perception of persistence between Cayenne and the frameworks with EJB background. > * postPersist() is defined in the JPA to occur after a NEW record is > written to the database. Which is how Cayenne works. Yep. > * pre/postUpdate(): the JPA specification is very very unclear, but > it looks like maybe it might apply for both new and existing > records. But then postUpdate() overlaps postPersist() which is a bit > pointless. I think this is left up to implementation to handle. Yes - confusing. > * if preUpdate() is just for existing records we still need a > preInsert(). I wonder if we should just call "preUpdate" for new objects in addition to prePersist. Andrus |
||||||||||||||||
|
Lachlan Deck
|
Hi there,
On 29/11/2007, at 11:22 PM, Andrus Adamchik wrote: > On Nov 29, 2007, at 10:56 AM, Aristedes Maniatis wrote: > >>> * prePersist() is only useful as a place to set object attributes >>> (such as creationDate) since you cannot follow relations reliably >>> in a ROP environment. > > True, and a workaround would be to support relationships for > transient objects. Something Kevin was bringing up on multiple > occasions. Yeah the prePersist on server (from ROP commit) would just give you a chance to initialise things that the client is unaware of I would've thought. Otherwise, there are no relationships to follow, really... as they've not been initialised yet. i.e., it's a new object that's being initialised / inserted into a context. The fact that you see the attributes already populated on server when committing from the client is because the prePersist in 3 tier on server is really a second constructor (or third). i.e., in the real java constructor you do nothing because the object has not as yet been inserted into an editing context, in prePersist on client you initialise attributes/ relationships, in prePersist on server, you can initialise other stuff if needed... but you don't yet have in view the object as it was on the client (i.e., relationships aren't faulted). That fulfils most general cases. >>> * postUpdate() and postPersist() are useful for changes which do >>> not need to be committed atomically with the original commit. So >>> good for creating log records, but not ideal for updating >>> invoiceOwing. > > Another way to go about atomic commits is to set up a manual > transaction that spans a request scope. I think that's an > appropriate solution. Making such changes in another context (in post[Persist|Update| Remove]) seems like reasonable practice, the question is when will such changes be reflected in the former context. >>> * postPersist() is badly named. It is really postInsert() >>> * we need preInsert() > > per JPA "persist" is "insert", so the naming is correct if somewhat > confusing. Hmm. Yeah I don't think it's badly named either. It's the counterpart, prePersist, that doesn't match conceptually (as with preUpdate) in terms of when it is fired. The point is that we're missing (logically) a callback. i.e., there does need to be a callback that prePersist currently provides for when objects are initiated, but there's no complimentary callback to preUpdate for those new objects just prior to commit. Perhaps the JPA assumes you'll initiate your objects as per normal Java via Constructors and such whereas in Cayenne you don't touch the object until it's been inserted into a Context and thus the current prePersist is your constructor. >> * if preUpdate() is just for existing records we still need a >> preInsert(). > > I wonder if we should just call "preUpdate" for new objects in > addition to prePersist. That doesn't sound like a nice fit. i.e., they're currently in nice pairs like: preUpdate --> postUpdate preRemove --> postRemove It would seem (to me at least), then, that you'd want (fired at the same times as above): prePersist --> postPersist And replace current prePersist with something like postNew or postCreate or something. So calling preUpdate would be better than nothing, if you're constrained by the JPA, but doesn't seem like the clean solution FWIW. What do you think? with regards, -- Lachlan Deck |
||||||||||||||||
|
Andrus Adamchik
|
In reply to this post
by Aristedes Maniatis
Having run into some issues with callbacks myself and generally
gaining better insight into callbacks patterns, I second what Ari has suggested two years ago in the message below. Namely I suggest the following changes: 1. call pre/postUpdate for new objects. So new objects will have pre/ postPersist as well as pre/postUpdate callbacks. Of course the main motivation is that preUpdate is called before save, not after context insert, so all the relationships are in place. 2. stop calling pre/postUpdate for removed objects. This is causing grief when we need to access relationships of a deleted object. Both changes are actually still compatible with JPA spec, due to the vagueness mentioned below. This may affect the callbacks currently in use by 3.0 applications, so this is our last chance to slip it in before the beta, and also we need to make it clear what upgrade steps need to be taken by the current 3.0 users. If there's no objections, I will work on such change. (Also not sure how this affects ROP yet?) Andrus On Nov 29, 2007, at 10:56 AM, Aristedes Maniatis wrote: > On 29/11/2007, at 12:28 PM, Aristedes Maniatis wrote: > >> Summary >> ------- >> >> * prePersist() is only useful as a place to set object attributes >> (such as creationDate) since you cannot follow relations reliably >> in a ROP environment. >> >> * postUpdate() and postPersist() are useful for changes which do >> not need to be committed atomically with the original commit. So >> good for creating log records, but not ideal for updating >> invoiceOwing. >> >> * postPersist() is badly named. It is really postInsert() >> >> * we need preInsert() > > > With some further reading of the JPA specification I have come to > the following conclusions: > > * the JPA was written by lawyers who get paid by the word > > * postPersist() is defined in the JPA to occur after a NEW record is > written to the database. Which is how Cayenne works. > > * pre/postUpdate(): the JPA specification is very very unclear, but > it looks like maybe it might apply for both new and existing > records. But then postUpdate() overlaps postPersist() which is a bit > pointless. > > * if preUpdate() is just for existing records we still need a > preInsert(). > > > So, even though we have to follow the poorly named callbacks in JPA, > an extra callback would fill a useful hole. > > > Ari > > > --------------------------> > ish > http://www.ish.com.au > Level 1, 30 Wilson Street Newtown 2042 Australia > phone +61 2 9550 5001 fax +61 2 9550 4001 > GPG fingerprint CBFB 84B4 738D 4E87 5E5C 5EFA EF6A 7D2E 3E49 102A > > > |
||||||||||||||||
|
Andrus Adamchik
|
> This is causing grief when we need to access relationships of a
> deleted object. Better wording: This is causing grief when we implement a post-update callback that needs to traverse relationships, and the object is already deleted, so no relationships are accessible. On Sep 23, 2009, at 3:32 PM, Andrus Adamchik wrote: > Having run into some issues with callbacks myself and generally > gaining better insight into callbacks patterns, I second what Ari > has suggested two years ago in the message below. Namely I suggest > the following changes: > > 1. call pre/postUpdate for new objects. So new objects will have pre/ > postPersist as well as pre/postUpdate callbacks. Of course the main > motivation is that preUpdate is called before save, not after > context insert, so all the relationships are in place. > > 2. stop calling pre/postUpdate for removed objects. This is causing > grief when we need to access relationships of a deleted object. > > Both changes are actually still compatible with JPA spec, due to the > vagueness mentioned below. This may affect the callbacks currently > in use by 3.0 applications, so this is our last chance to slip it in > before the beta, and also we need to make it clear what upgrade > steps need to be taken by the current 3.0 users. > > If there's no objections, I will work on such change. > > (Also not sure how this affects ROP yet?) > > Andrus > > > On Nov 29, 2007, at 10:56 AM, Aristedes Maniatis wrote: >> On 29/11/2007, at 12:28 PM, Aristedes Maniatis wrote: >> >>> Summary >>> ------- >>> >>> * prePersist() is only useful as a place to set object attributes >>> (such as creationDate) since you cannot follow relations reliably >>> in a ROP environment. >>> >>> * postUpdate() and postPersist() are useful for changes which do >>> not need to be committed atomically with the original commit. So >>> good for creating log records, but not ideal for updating >>> invoiceOwing. >>> >>> * postPersist() is badly named. It is really postInsert() >>> >>> * we need preInsert() >> >> >> With some further reading of the JPA specification I have come to >> the following conclusions: >> >> * the JPA was written by lawyers who get paid by the word >> >> * postPersist() is defined in the JPA to occur after a NEW record >> is written to the database. Which is how Cayenne works. >> >> * pre/postUpdate(): the JPA specification is very very unclear, but >> it looks like maybe it might apply for both new and existing >> records. But then postUpdate() overlaps postPersist() which is a >> bit pointless. >> >> * if preUpdate() is just for existing records we still need a >> preInsert(). >> >> >> So, even though we have to follow the poorly named callbacks in >> JPA, an extra callback would fill a useful hole. >> >> >> Ari >> >> >> --------------------------> >> ish >> http://www.ish.com.au >> Level 1, 30 Wilson Street Newtown 2042 Australia >> phone +61 2 9550 5001 fax +61 2 9550 4001 >> GPG fingerprint CBFB 84B4 738D 4E87 5E5C 5EFA EF6A 7D2E 3E49 102A >> >> >> > > |
||||||||||||||||
|
Andrey Razumovsky
|
Hi Andrus,
I also suffered missing "preInsert" listener a number of times. But, I think the moment of when listeners are called (and which of them are called) should be easily guessed by what is happening to the object. 1. call pre/postUpdate for new objects. So new objects will have >> pre/postPersist as well as pre/postUpdate callbacks. Of course the main >> motivation is that preUpdate is called before save, not after context >> insert, so all the relationships are in place. >> >> So there will be four calls in total at object creation. I don't think it is transparent that pre- and postUpdate will be called - after all, no updates are ever made! > 2. stop calling pre/postUpdate for removed objects. This is causing grief >> when we need to access relationships of a deleted object. >> > I'm confused. Are pre/postUpdate methods really called for deleted objects? If this is so, it definitely must be stopped I think. Only pre/postRemove should be called.. What will be wrong if we just add new type of event - preInsert? Users aren't obligated to implement any listener class, after all. I'm afraid the changes you suggest will break a lot of existing code Thanks, |
||||||||||||||||
|
Andrus Adamchik
|
Hi Andrey,
Thanks for your comments. This made me tweak my initial suggestion, resulting in something that hopefully makes more sense. So here is an attempt to show the full callback picture with suggested changes (grouping things by the object pre-commit state): COMMITTED: postLoad NEW: prePersist (called after registration in context) preInsert (POTENTIAL NEW CALLBACK; called before commit) [1] postPersist (called after commit) MODIFIED: preUpdate (called before commit) postUpdate (called after commit) DELETED: preRemove (called before deleting in context, and before delete rules are evaluated) preUpdate (POTENTIAL REMOVED CALLBACK; called before commit) [2] postRemove (called after commit) postUpdate (POTENTIAL REMOVED CALLBACK; called after commit) [3] [1] We definitely need this callback here for the new objects right before commit. My suggestion of reusing "preUpdate" here was based on the fact that all my pre-commit code for NEW and MODIFIED objects is exactly the same, so it seems that from implementation standpoint the event is almost identical. On the other hand, "postUpdate" is indeed redundant for NEW objects. So using pre/postUpdate for NEW does break the symmetry... [2] While there's no overlap with a real "preUpdate" in possible callback functionality here, and we need to remove this one, this leaves a gap (just like with NEW objects) that needs to be filled with some "preCommitDeleted" callback. [3] Definitely needs to be removed. It is being passed TRANSIENT objects and is really out of place (though still JPA compatible :-)). I guess completely renaming callbacks to be aligned with Cayenne (rather than JPA) lifecycle could be a good thing (persist -> insert| create, remove -> delete, etc.), and may go a long way in clarifying what is called and when. It is the end of a long day here, so I am not ready to suggest any better naming right away, but regardless of the naming, if we just make the changes above, I think we can provide the existing users a clear set of instructions on how to migrate existing callbacks to the new set. FWIW I'll be able to try it myself on a large application without waiting for the B1 release. Andrus On Sep 23, 2009, at 8:53 PM, Andrey Razumovsky wrote: > Hi Andrus, > > I also suffered missing "preInsert" listener a number of times. But, > I think > the moment of when listeners are called (and which of them are called) > should be easily guessed by what is happening to the object. > > 1. call pre/postUpdate for new objects. So new objects will have >>> pre/postPersist as well as pre/postUpdate callbacks. Of course the >>> main >>> motivation is that preUpdate is called before save, not after >>> context >>> insert, so all the relationships are in place. >>> >>> > So there will be four calls in total at object creation. I don't > think it is > transparent that pre- and postUpdate will be called - after all, no > updates > are ever made! > > >> 2. stop calling pre/postUpdate for removed objects. This is causing >> grief >>> when we need to access relationships of a deleted object. >>> >> > I'm confused. Are pre/postUpdate methods really called for deleted > objects? > If this is so, it definitely must be stopped I think. Only pre/ > postRemove > should be called.. > > What will be wrong if we just add new type of event - preInsert? Users > aren't obligated to implement any listener class, after all. I'm > afraid the > changes you suggest will break a lot of existing code > > Thanks, |
||||||||||||||||
|
Lachlan Deck
|
On 24/09/2009, at 6:14 AM, Andrus Adamchik wrote:
> Hi Andrey, > > Thanks for your comments. This made me tweak my initial suggestion, > resulting in something that hopefully makes more sense. So here is > an attempt to show the full callback picture with suggested changes > (grouping things by the object pre-commit state): > > COMMITTED: > postLoad > > NEW: > prePersist (called after registration in context) > preInsert (POTENTIAL NEW CALLBACK; called before commit) [1] > postPersist (called after commit) The naming of these is a little less unintuitive though. What about: postInsert (called after registration in context) prePersist (called just before commit, but after validation) postPersist (called after commit) This - naming-wise - would fit better with the modified callbacks, no? > MODIFIED: > preUpdate (called before commit) > postUpdate (called after commit) > > DELETED: > preRemove (called before deleting in context, and before delete > rules are evaluated) > preUpdate (POTENTIAL REMOVED CALLBACK; called before commit) [2] > postRemove (called after commit) > postUpdate (POTENTIAL REMOVED CALLBACK; called after commit) [3] > > > [1] We definitely need this callback here for the new objects right > before commit. My suggestion of reusing "preUpdate" here was based > on the fact that all my pre-commit code for NEW and MODIFIED objects > is exactly the same, so it seems that from implementation standpoint > the event is almost identical. On the other hand, "postUpdate" is > indeed redundant for NEW objects. So using pre/postUpdate for NEW > does break the symmetry... > > [2] While there's no overlap with a real "preUpdate" in possible > callback functionality here, and we need to remove this one, this > leaves a gap (just like with NEW objects) that needs to be filled > with some "preCommitDeleted" callback. > > [3] Definitely needs to be removed. It is being passed TRANSIENT > objects and is really out of place (though still JPA compatible :-)). > > I guess completely renaming callbacks to be aligned with Cayenne > (rather than JPA) lifecycle could be a good thing (persist -> insert| > create, remove -> delete, etc.), and may go a long way in clarifying > what is called and when. It is the end of a long day here, so I am > not ready to suggest any better naming right away, but regardless of > the naming, if we just make the changes above, I think we can > provide the existing users a clear set of instructions on how to > migrate existing callbacks to the new set. FWIW I'll be able to try > it myself on a large application without waiting for the B1 release. > > Andrus > > > > On Sep 23, 2009, at 8:53 PM, Andrey Razumovsky wrote: > >> Hi Andrus, >> >> I also suffered missing "preInsert" listener a number of times. >> But, I think >> the moment of when listeners are called (and which of them are >> called) >> should be easily guessed by what is happening to the object. >> >> 1. call pre/postUpdate for new objects. So new objects will have >>>> pre/postPersist as well as pre/postUpdate callbacks. Of course >>>> the main >>>> motivation is that preUpdate is called before save, not after >>>> context >>>> insert, so all the relationships are in place. >>>> >>>> >> So there will be four calls in total at object creation. I don't >> think it is >> transparent that pre- and postUpdate will be called - after all, no >> updates >> are ever made! >> >> >>> 2. stop calling pre/postUpdate for removed objects. This is >>> causing grief >>>> when we need to access relationships of a deleted object. >>>> >>> >> I'm confused. Are pre/postUpdate methods really called for deleted >> objects? >> If this is so, it definitely must be stopped I think. Only pre/ >> postRemove >> should be called.. >> >> What will be wrong if we just add new type of event - preInsert? >> Users >> aren't obligated to implement any listener class, after all. I'm >> afraid the >> changes you suggest will break a lot of existing code >> >> Thanks, > with regards, -- Lachlan Deck |
||||||||||||||||
|
Lachlan Deck
|
In reply to this post
by Andrus Adamchik
On 23/09/2009, at 10:32 PM, Andrus Adamchik wrote:
> (Also not sure how this affects ROP yet?) Hopefully it'd mirror the server-side as closely as possible. with regards, -- Lachlan Deck |
||||||||||||||||
|
Aristedes Maniatis-2
|
On 24/09/09 10:16 AM, Lachlan Deck wrote:
> On 23/09/2009, at 10:32 PM, Andrus Adamchik wrote: > >> (Also not sure how this affects ROP yet?) > > Hopefully it'd mirror the server-side as closely as possible. Some things to think about: 1. When an object is modified in a callback on the server, it should be updated by Cayenne automatically on the client. I don't think this currently happens. 2. When using ROP, the server side callbacks are a bit different. That is, you don't get prePersist until the object is committed, so I would propose that that callback be skipped completely on the server in ROP. Is this the sequence? Client: prePersist [user code to build and save context] Client: preInsert Server: preInsert Server: postPersist Client: postPersist I think the naming is inconsistent: "persist", "insert" are used interchangably here. Ari -- --------------------------> Aristedes Maniatis GPG fingerprint CBFB 84B4 738D 4E87 5E5C 5EFA EF6A 7D2E 3E49 102A |
||||||||||||||||
|
Robert Zeigler-6
|
In reply to this post
by Andrus Adamchik
Hm. Apologies, I haven't been following this thread very closely.
Preface: I realize JPA compatibility isn't necessarily a current goal, but hear me out. :) The way I read the JPA spec is that @PrePersist is supposed to be called immediately before database insert. The problem seems to be a mismatch between the "cayenne way" and the "JPA" way. In cayenne, you register your object, then make your changes (especially relationships), then commit to the DB. In JPA, you would setup the entire bean structure (including relationships) before calling EntityManager.persist, which results directly in a DB commit. Ie, persist is like context.registerNewObject() + context.commitChanges() rolled into one, except the scope is limited a single object and associated changes. I don't have any qualms about adding additional lifecycle listeners to make cayenne more useful in general and/or to address the impedence with JPA, but if we're going to keep lifecycle listeners with the same names as the JPA ones, we should also keep the same semantics, which means calling PrePersist immediately before insert. Otherwise, we'll confuse users new to cayenne but familiar with JPA. So, I would propose something more like: postRegister <- called when a non-persisted object is registered with a data context. You could even have a preRegister, but that's probably overkill. prePersist <- just before DB insert postPersist <- just after DB insert preUpdate < - just before DB update of existing object postUpdate <- just after DB update preRemove <- just before DB delete postRemove <-just after DB delete postLoad <- just after a committed obj. is fetched from the DB. Alternatively, as mentioned below we could rename all callbacks to align with the "cayenne way". But, again, if we're going to keep the JPA names, we should keep the behavior as JPA-like as possible. Robert On Sep 23, 2009, at 9/233:14 PM , Andrus Adamchik wrote: > Hi Andrey, > > Thanks for your comments. This made me tweak my initial suggestion, > resulting in something that hopefully makes more sense. So here is > an attempt to show the full callback picture with suggested changes > (grouping things by the object pre-commit state): > > COMMITTED: > postLoad > > NEW: > prePersist (called after registration in context) > preInsert (POTENTIAL NEW CALLBACK; called before commit) [1] > postPersist (called after commit) > > MODIFIED: > preUpdate (called before commit) > postUpdate (called after commit) > > DELETED: > preRemove (called before deleting in context, and before delete > rules are evaluated) > preUpdate (POTENTIAL REMOVED CALLBACK; called before commit) [2] > postRemove (called after commit) > postUpdate (POTENTIAL REMOVED CALLBACK; called after commit) [3] > > > [1] We definitely need this callback here for the new objects right > before commit. My suggestion of reusing "preUpdate" here was based > on the fact that all my pre-commit code for NEW and MODIFIED objects > is exactly the same, so it seems that from implementation standpoint > the event is almost identical. On the other hand, "postUpdate" is > indeed redundant for NEW objects. So using pre/postUpdate for NEW > does break the symmetry... > > [2] While there's no overlap with a real "preUpdate" in possible > callback functionality here, and we need to remove this one, this > leaves a gap (just like with NEW objects) that needs to be filled > with some "preCommitDeleted" callback. > > [3] Definitely needs to be removed. It is being passed TRANSIENT > objects and is really out of place (though still JPA compatible :-)). > > I guess completely renaming callbacks to be aligned with Cayenne > (rather than JPA) lifecycle could be a good thing (persist -> insert| > create, remove -> delete, etc.), and may go a long way in clarifying > what is called and when. It is the end of a long day here, so I am > not ready to suggest any better naming right away, but regardless of > the naming, if we just make the changes above, I think we can > provide the existing users a clear set of instructions on how to > migrate existing callbacks to the new set. FWIW I'll be able to try > it myself on a large application without waiting for the B1 release. > > Andrus > > > > On Sep 23, 2009, at 8:53 PM, Andrey Razumovsky wrote: > >> Hi Andrus, >> >> I also suffered missing "preInsert" listener a number of times. >> But, I think >> the moment of when listeners are called (and which of them are >> called) >> should be easily guessed by what is happening to the object. >> >> 1. call pre/postUpdate for new objects. So new objects will have >>>> pre/postPersist as well as pre/postUpdate callbacks. Of course >>>> the main >>>> motivation is that preUpdate is called before save, not after >>>> context >>>> insert, so all the relationships are in place. >>>> >>>> >> So there will be four calls in total at object creation. I don't >> think it is >> transparent that pre- and postUpdate will be called - after all, no >> updates >> are ever made! >> >> >>> 2. stop calling pre/postUpdate for removed objects. This is >>> causing grief >>>> when we need to access relationships of a deleted object. >>>> >>> >> I'm confused. Are pre/postUpdate methods really called for deleted >> objects? >> If this is so, it definitely must be stopped I think. Only pre/ >> postRemove >> should be called.. >> >> What will be wrong if we just add new type of event - preInsert? >> Users >> aren't obligated to implement any listener class, after all. I'm >> afraid the >> changes you suggest will break a lot of existing code >> >> Thanks, > |
||||||||||||||||
|
Andrey Razumovsky
|
I feel the most comfortable with Robert's suggestion. Currently it is very
unintuitive that postPersist is called miles away from prePersist (and might be not called at all). This will require some revision of already written listeners, but it's quite benign. 2009/9/24 Robert Zeigler <[hidden email]> > Hm. Apologies, I haven't been following this thread very closely. Preface: > I realize JPA compatibility isn't necessarily a current goal, but hear me > out. :) > The way I read the JPA spec is that @PrePersist is supposed to be called > immediately before database insert. > The problem seems to be a mismatch between the "cayenne way" and the "JPA" > way. In cayenne, you register your object, then make your changes > (especially relationships), then commit to the DB. In JPA, you would setup > the entire bean structure (including relationships) before calling > EntityManager.persist, which results directly in a DB commit. Ie, persist > is like context.registerNewObject() + context.commitChanges() rolled into > one, except the scope is limited a single object and associated changes. I > don't have any qualms about adding additional lifecycle listeners to make > cayenne more useful in general and/or to address the impedence with JPA, but > if we're going to keep lifecycle listeners with the same names as the JPA > ones, we should also keep the same semantics, which means calling PrePersist > immediately before insert. Otherwise, we'll confuse users new to cayenne > but familiar with JPA. So, I would propose something more like: > > postRegister <- called when a non-persisted object is registered with a > data context. You could even have a preRegister, but that's probably > overkill. > prePersist <- just before DB insert > postPersist <- just after DB insert > preUpdate < - just before DB update of existing object > postUpdate <- just after DB update > preRemove <- just before DB delete > postRemove <-just after DB delete > postLoad <- just after a committed obj. is fetched from the DB. > > Alternatively, as mentioned below we could rename all callbacks to align > with the "cayenne way". But, again, if we're going to keep the JPA names, > we should keep the behavior as JPA-like as possible. > > Robert > > > On Sep 23, 2009, at 9/233:14 PM , Andrus Adamchik wrote: > > Hi Andrey, >> >> Thanks for your comments. This made me tweak my initial suggestion, >> resulting in something that hopefully makes more sense. So here is an >> attempt to show the full callback picture with suggested changes (grouping >> things by the object pre-commit state): >> >> COMMITTED: >> postLoad >> >> NEW: >> prePersist (called after registration in context) >> preInsert (POTENTIAL NEW CALLBACK; called before commit) [1] >> postPersist (called after commit) >> >> MODIFIED: >> preUpdate (called before commit) >> postUpdate (called after commit) >> >> DELETED: >> preRemove (called before deleting in context, and before delete rules are >> evaluated) >> preUpdate (POTENTIAL REMOVED CALLBACK; called before commit) [2] >> postRemove (called after commit) >> postUpdate (POTENTIAL REMOVED CALLBACK; called after commit) [3] >> >> >> [1] We definitely need this callback here for the new objects right before >> commit. My suggestion of reusing "preUpdate" here was based on the fact that >> all my pre-commit code for NEW and MODIFIED objects is exactly the same, so >> it seems that from implementation standpoint the event is almost identical. >> On the other hand, "postUpdate" is indeed redundant for NEW objects. So >> using pre/postUpdate for NEW does break the symmetry... >> >> [2] While there's no overlap with a real "preUpdate" in possible callback >> functionality here, and we need to remove this one, this leaves a gap (just >> like with NEW objects) that needs to be filled with some "preCommitDeleted" >> callback. >> >> [3] Definitely needs to be removed. It is being passed TRANSIENT objects >> and is really out of place (though still JPA compatible :-)). >> >> I guess completely renaming callbacks to be aligned with Cayenne (rather >> than JPA) lifecycle could be a good thing (persist -> insert|create, remove >> -> delete, etc.), and may go a long way in clarifying what is called and >> when. It is the end of a long day here, so I am not ready to suggest any >> better naming right away, but regardless of the naming, if we just make the >> changes above, I think we can provide the existing users a clear set of >> instructions on how to migrate existing callbacks to the new set. FWIW I'll >> be able to try it myself on a large application without waiting for the B1 >> release. >> >> Andrus >> >> >> >> On Sep 23, 2009, at 8:53 PM, Andrey Razumovsky wrote: >> >> Hi Andrus, >>> >>> I also suffered missing "preInsert" listener a number of times. But, I >>> think >>> the moment of when listeners are called (and which of them are called) >>> should be easily guessed by what is happening to the object. >>> >>> 1. call pre/postUpdate for new objects. So new objects will have >>> >>>> pre/postPersist as well as pre/postUpdate callbacks. Of course the main >>>>> motivation is that preUpdate is called before save, not after context >>>>> insert, so all the relationships are in place. >>>>> >>>>> >>>>> So there will be four calls in total at object creation. I don't think >>> it is >>> transparent that pre- and postUpdate will be called - after all, no >>> updates >>> are ever made! >>> >>> >>> 2. stop calling pre/postUpdate for removed objects. This is causing >>>> grief >>>> >>>>> when we need to access relationships of a deleted object. >>>>> >>>>> >>>> I'm confused. Are pre/postUpdate methods really called for deleted >>> objects? >>> If this is so, it definitely must be stopped I think. Only pre/postRemove >>> should be called.. >>> >>> What will be wrong if we just add new type of event - preInsert? Users >>> aren't obligated to implement any listener class, after all. I'm afraid >>> the >>> changes you suggest will break a lot of existing code >>> >>> Thanks, >>> >> >> > -- Andrey |
||||||||||||||||
|
Andrus Adamchik
|
In reply to this post
by Robert Zeigler-6
Actually EntityManager.persist is the same as
DataContext.registerObject. It does not commit to DB until "flush" is called or external transaction is committed. But the rest of the analysis is correct. So I am +1 on the following, even though there we'll end up with mismatch against JPA (Cayenne.postRegister == JPA.prePersist; Cayenne.prePersist != JPA.prePersist) : > postRegister <- called when a non-persisted object is registered > with a data context. You could even have a preRegister, but that's > probably overkill. > prePersist <- just before DB insert > postPersist <- just after DB insert > preUpdate < - just before DB update of existing object > postUpdate <- just after DB update > postLoad <- just after a committed obj. is fetched from the DB. Now wonder what we should do with "remove": > preRemove <- just before DB delete > postRemove <-just after DB delete There are 3 similar lifecycle points: (1) after object is deleted in context, (2) before deleted object is committed, (3) after deleted object is committed. Right now "preRemove" is called at (1). For symmetry we can call it at (2). But then we need another callback for (1). I just checked some of my use cases, and (1) is sort of an important place - you can make some assertions about the object before it is disassociated from its relationships via delete rules. I think I already figured out how to deal with my case, not sure how such removal of functionality would affect others? BTW, if all we do is callback renaming/adding new callbacks, we can provide DataMap migration ability via the Modeler. Andrus On Sep 24, 2009, at 6:08 AM, Robert Zeigler wrote: > Hm. Apologies, I haven't been following this thread very closely. > Preface: I realize JPA compatibility isn't necessarily a current > goal, but hear me out. :) > The way I read the JPA spec is that @PrePersist is supposed to be > called immediately before database insert. > The problem seems to be a mismatch between the "cayenne way" and the > "JPA" way. In cayenne, you register your object, then make your > changes (especially relationships), then commit to the DB. In JPA, > you would setup the entire bean structure (including relationships) > before calling EntityManager.persist, which results directly in a DB > commit. Ie, persist is like context.registerNewObject() + > context.commitChanges() rolled into one, except the scope is limited > a single object and associated changes. > I don't have any qualms about adding additional lifecycle listeners > to make cayenne more useful in general and/or to address the > impedence with JPA, but if we're going to keep lifecycle listeners > with the same names as the JPA ones, we should also keep the same > semantics, which means calling PrePersist immediately before > insert. Otherwise, we'll confuse users new to cayenne but familiar > with JPA. So, I would propose something more like: > > postRegister <- called when a non-persisted object is registered > with a data context. You could even have a preRegister, but that's > probably overkill. > prePersist <- just before DB insert > postPersist <- just after DB insert > preUpdate < - just before DB update of existing object > postUpdate <- just after DB update > preRemove <- just before DB delete > postRemove <-just after DB delete > postLoad <- just after a committed obj. is fetched from the DB. > > Alternatively, as mentioned below we could rename all callbacks to > align with the "cayenne way". But, again, if we're going to keep > the JPA names, we should keep the behavior as JPA-like as possible. > > Robert |
||||||||||||||||
|
Aristedes Maniatis-2
|
On 24/09/09 6:08 PM, Andrus Adamchik wrote:
> So I am +1 on the following, even though there we'll end up with > mismatch against JPA (Cayenne.postRegister == > JPA.prePersist; Cayenne.prePersist != JPA.prePersist) Or, to take Robert's suggestion, we leave the existing callbacks in place and don't touch them since they are the JPA terms. New stuff can have brand new names. To make a much more radical suggestion: Working within the context: onAddToContext [postRegister at the moment] onRemoveFromContext [doesn't exist now, but seems symmetrical] onDelete [deleted in context] beforeCommit [preRemove, prePersist and preUpdate together] afterCommit [postRemove, postPersist and postUpdate together] onFetch [postLoad at the moment] If we use words (like "commit") which already have meaning to Cayenne users, then it is very clear what is going on and it separates them from the JPA words. Then our docs have a little table with two columns, showing where our terms match the JPA terms exactly and where they differ. Avoiding "pre" and "post" completely keeps our terms quite separate. Then we don't even need to deprecate the JPA terms at all, just keep them as an 'alias' to Cayenne terms where appropriate. Let me explain my rationale for merging remove, persist and update. It is easily possible within the callback itself to determine the state of the object, so the user can write code which branches appropriately. We find ourselves needing to link both persist and update together anyway, because it is likely that the same code will run for both. Next, the user action which caused the callback to happen was commit(), so it just makes logical sense. Finally, it means that the context itself can have its own beforeCommit and afterCommit callbacks (with the same names so it all makes consistent sense!): beforeCommit -> object A beforeCommit -> object B beforeCommit -> context ...DB activity... afterCommit -> object A afterCommit -> object B afterCommit -> context This is a bit more work in the modeller to keep both JPA and Cayenne callbacks clear, but the end result might be more understandable, even if we end up with beforeCommitDelete, beforeCommitInsert, beforeCommitUpdate. Ari -- --------------------------> Aristedes Maniatis GPG fingerprint CBFB 84B4 738D 4E87 5E5C 5EFA EF6A 7D2E 3E49 102A |
||||||||||||||||
|
Andrus Adamchik
|
I am +1 on:
* Radical renaming if that helps us with consistency. * The observation that pre/postCommit for NEW and MODIFIED objects are often identical (in my use cases they are always identical). Wonder in what case it is important to distinguish in postCommit whether the object was new or modified before commit? I am -1 on: * Keeping two parallel sets of callbacks. The only purpose it serves is compatibility with 3.0MN releases (N <= 6), and the need to maintain it going forward outweighs it in my mind. Also triggering callbacks is not free performance-wise, even for those events that have no registered listeners, so extra callback types will (marginally) slow things down. This will also allow us to use "pre/ post" prefixes for the names, as there won't parallel conflicting set of callbacks. * Mixing pre/postCommit for DELETED with NEW/MODIFIED. The object state at the end of the commit is different (TRANSIENT for DELETED, COMMITTED for NEW/MODIFIED), so callbacks are almost never the same. I am +/-0 on: * Callbacks on contexts. Could be a good idea to have listeners attached to a context (although I don't see any use for context's own callback methods). I suggest we keep that in mind, but push it to the future releases to avoid adding new concepts to 3.0 at this point. Conclusion: So mixing Ari's idea with my points above, we have something like that: * postLoad * postAdd ("post" as it is invoked after object is added to the context) * preDelete ("pre" as it is invoked before delete rules are processed and before the object state is changed) * preCommit * preCommitDeleted * postCommit * postCommitDeleted ... or we can go completely the Cayenne way and change the callback signatures to take an enum characterizing the event nature, or even some sort of an event object. And another idea. Once we settle on the nature of the change on this list, we write a migration guide draft for the users and take this discussion to the user@ list to see if the wider community can uncover any holes in the new design, or if there are other use cases that we haven't thought of. Andrus On Sep 24, 2009, at 12:54 PM, Aristedes Maniatis wrote: > On 24/09/09 6:08 PM, Andrus Adamchik wrote: >> So I am +1 on the following, even though there we'll end up with >> mismatch against JPA (Cayenne.postRegister == >> JPA.prePersist; Cayenne.prePersist != JPA.prePersist) > > Or, to take Robert's suggestion, we leave the existing callbacks in > place and don't touch them since they are the JPA terms. New stuff > can have brand new names. > > To make a much more radical suggestion: > > Working within the context: > onAddToContext [postRegister at the moment] > onRemoveFromContext [doesn't exist now, but seems symmetrical] > > onDelete [deleted in context] > > beforeCommit [preRemove, prePersist and preUpdate together] > afterCommit [postRemove, postPersist and postUpdate together] > > onFetch [postLoad at the moment] > > > If we use words (like "commit") which already have meaning to > Cayenne users, then it is very clear what is going on and it > separates them from the JPA words. Then our docs have a little table > with two columns, showing where our terms match the JPA terms > exactly and where they differ. Avoiding "pre" and "post" completely > keeps our terms quite separate. Then we don't even need to deprecate > the JPA terms at all, just keep them as an 'alias' to Cayenne terms > where appropriate. > > Let me explain my rationale for merging remove, persist and update. > It is easily possible within the callback itself to determine the > state of the object, so the user can write code which branches > appropriately. We find ourselves needing to link both persist and > update together anyway, because it is likely that the same code will > run for both. Next, the user action which caused the callback to > happen was commit(), so it just makes logical sense. Finally, it > means that the context itself can have its own beforeCommit and > afterCommit callbacks (with the same names so it all makes > consistent sense!): > > beforeCommit -> object A > beforeCommit -> object B > beforeCommit -> context > ...DB activity... > afterCommit -> object A > afterCommit -> object B > afterCommit -> context > > > This is a bit more work in the modeller to keep both JPA and Cayenne > callbacks clear, but the end result might be more understandable, > even if we end up with beforeCommitDelete, beforeCommitInsert, > beforeCommitUpdate. > > > Ari > > > -- > > --------------------------> > Aristedes Maniatis > GPG fingerprint CBFB 84B4 738D 4E87 5E5C 5EFA EF6A 7D2E 3E49 102A > |
||||||||||||||||
|
Lachlan Deck
|
On 28/09/2009, at 12:13 AM, Andrus Adamchik wrote:
> I am +1 on: > > * Radical renaming if that helps us with consistency. > * The observation that pre/postCommit for NEW and MODIFIED objects > are often identical (in my use cases they are always identical). > Wonder in what case it is important to distinguish in postCommit > whether the object was new or modified before commit? I'm hoping these combined callbacks are not replacing individual ones. > <..> > Conclusion: > > So mixing Ari's idea with my points above, we have something like > that: > > * postLoad > > * postAdd ("post" as it is invoked after object is added to the > context) > * preDelete ("pre" as it is invoked before delete rules are > processed and before the object state is changed) you?) to keep it balanced. > * preCommit > * preCommitDeleted > * postCommit > * postCommitDeleted > > ... or we can go completely the Cayenne way and change the callback > signatures to take an enum characterizing the event nature, or even > some sort of an event object. Not personally keen on if-else based lifecycle callbacks... but I suppose everyone's superclass can call the relevant signature. Just to review the thread.... I'm not sure such a drastic change is needed to make lifecycle callbacks complete/useful. There was one (maybe two) missing and some adjusted behaviour / clarification needed overall, no(?) rather than a complete change? i.e., it seems to me it's mostly good but with some gaps postLoad .. after fetch prePersist ... should be instead called (whether ROP or not) after validateForSave but before persisting a new object preUpdate ... as now, after validation and before persisting updates to an already persisted object preDelete ... again after validation but prior to deleting from the objectstore postPersist postUpdate postDelete .. all logically matching their pre counterparts. what's missing? postInsert .. called after an object is created and inserted into a context So from what I can see the only two changes required were 'postInsert' and adjusting the meaning of prePersist with regards, -- Lachlan Deck |
||||||||||||||||
|
Aristedes Maniatis-2
|
On 28/09/09 11:08 AM, Lachlan Deck wrote:
> So from what I can see the only two changes required were 'postInsert' > and adjusting the meaning of prePersist I think most of the discussion started from the point that we shouldn't 'adjust' the meaning of an event which is defined by JPA since it will be confusing to users from a JPA background. I'm not wedding to the idea I proposed: it was more to stimulate some thinking about what a completely fresh approach might look like. But thinking about it, the problem with beforeCommit as a name is that it will not have effect when committing child contexts, so it is potentially confusing. Lachlan's idea of keeping the changes minimal might be the simplest one for users. On 28/09/09 12:13 AM, Andrus Adamchik wrote: > * Callbacks on contexts. Could be a good idea to have listeners attached > to a context (although I don't see any use for context's own callback > methods). I suggest we keep that in mind, but push it to the > future releases to avoid adding new concepts to 3.0 at this point. Certainly it should be postponed, but thought about now so we have a plan. The use for this type of listener is when you want to inspect the entire context before commit rather than look at the commit per object. As an example in one of our projects we added a whole lot of code server-side (ROP) on saving a new Payment object, which checked to see if it was a credit card payment and then performed some action (like contacting the bank, checking for places available, etc). But we found that when we had a free product there was no Payment, but we still wanted to check for places available. So a hook at a context level could have been useful to identify that the commit was of new enrolments/students/etc and then allow the code to check for places, process payment, etc As it is, we worked around the problem by creating a $0 Payment, just so the listener and all our code could be fired off in a consistent way. We could have alternately created a different servlet call or used several other approaches, so it was never a big issue, just a potential use-case. Anyhow, I've said enough about this topic and I don't want this to turn into a bikeshed... I'm happy with whatever Andrus implements, which will be a good improvement whichever way is chosen. Ari Maniatis -- --------------------------> Aristedes Maniatis GPG fingerprint CBFB 84B4 738D 4E87 5E5C 5EFA EF6A 7D2E 3E49 102A |
||||||||||||||||
|
Andrus Adamchik
|
In reply to this post
by Lachlan Deck
On Sep 28, 2009, at 4:08 AM, Lachlan Deck wrote: > > So from what I can see the only two changes required were > 'postInsert' and adjusting the meaning of prePersist I also tend to think that less is better. This discussion thread was a nice brainstorming on the callbacks use patterns. So we've played with a few things and now I am fine if we go a full circle to the minimal change suggested earlier, and don't worry about JPA users, extra delete callbacks, or symmetry between callback types (in many respects there's little symmetry if you look at the object lifecycle - http://cayenne.apache.org/doc/persistent-object-lifecycle.html) . So here is the change I am going to make: 1. Call "prePersist" before commit 2. Introduce "postAdd" instead of current "prePersist". This change is minimal and is not incompatible with other ideas. Objections? Andrus |
||||||||||||||||
|
Lachlan Deck
|
On 28/09/2009, at 4:53 PM, Andrus Adamchik wrote:
> On Sep 28, 2009, at 4:08 AM, Lachlan Deck wrote: > >> >> So from what I can see the only two changes required were >> 'postInsert' and adjusting the meaning of prePersist > > I also tend to think that less is better. This discussion thread was > a nice brainstorming on the callbacks use patterns. So we've played > with a few things and now I am fine if we go a full circle to the > minimal change suggested earlier, and don't worry about JPA users, > extra delete callbacks, or symmetry between callback types (in many > respects there's little symmetry if you look at the object lifecycle > - http://cayenne.apache.org/doc/persistent-object-lifecycle.html). > > So here is the change I am going to make: > > 1. Call "prePersist" before commit > 2. Introduce "postAdd" instead of current "prePersist". > > This change is minimal and is not incompatible with other ideas. > Objections? No objections - just a suggestion that whilst looking at this stuff it'll be good to review ROP's behaviour with callbacks such that it's consistent with the server-side. with regards, -- Lachlan Deck |
||||||||||||||||
| Free Embeddable Forum Powered by Nabble | Help |