Anil Gangolli wrote:

Allen:

Your suggested pattern is workable, but you'll need to make begin() and commit() conditional and smart about transaction demarcation; it is a common pattern to conditionally begin a transaction only if there isn't one on the current thread, and you'll find that in many infrastructures (see my comments on Section 2 in the design proposal).


I agree with this and believe it's fairly easy to do.


What happens if you don't have that logic? You'll find that the operations that you choose to make transactional can't be easily composed, which is a very awkward property to have. If you ever need to compose two operations, you would need to write a third method that includes all of the operations of the originals, and not just calling the two original methods, but copying their innards. I actually went through a period of trying to code apps this way in the 90's before infrastructures like EJB started to normalize the conditional demarcation patterns in Java. Lesson learned.


You are correct, that is absolutely a possible ramification and we could end up making it more difficult to properly wrap some steps together in a transaction. However, I would also argue that the reverse scenerio is true for our current setup using more open transaction demarcation. Allowing business/presentation code to control transaction demarcation allows for transactions to become overly inclusive and actually end up including more operations than they should.


The patterns for transaction demarcation in the dispatch layers involve three parts: (1) demarcation for requests on a per-request basis in a Filter or some other centralized dispatch point (e.g. in Axis or Struts apps in extensions of these servlets wrapping the entry points) (2) for scheduled tasks, wrapping the run() method in a base class (3) providing a mechanism to get an independent transaction for individual units of work at smaller granularities (REQUIRES_NEW style semantics), which I expect would be used very rarely in Roller. Patterns like this work well for two-tier web apps like Roller where the persistence container and the webapp container are the same. They don't work (or require much stronger transaction infrastructure) if you separate the webapp container from the persistence container which does not apply in Roller's case.


In order to get a better grasp on some Hibernate details I am currently skimming through "Hibernate in Action" and they say ...

"We merely observe that, in our opinion, overengineering has been endemic in the Java community, and overly ambitious multilayered architectures have significanly contributed to the cose of Java development and to the perceived complexity of J2EE. On the other hand, we do agree that a dedicated persistence layer is a sensible choice for most applications and that persistence-related code shouldn't be moxed with business logic or presentation." (page 296)

In their examples for designing a layered application (webapp specifically) they do not provide transaction demarcation outside of the DAO layer. Of course this is only a single opinion and does not mean it's what's best for Roller, but I would say that my opinions are more in line with the sentiments from the quote above.

Speaking more specifically about applying transaction demarcation at points in the dispatch layer ... I think I still see this as allowing persistence logic into the business/presentation layer. The other reason why this makes me nervous is that I think it creates some potential for two situations ...

1. catching a persistence exception too late. if the transaction demarcation doesn't actually commit the transaction data until a while after the app thinks the data has been written then we may end up putting ourselves in a situation where we catch an exception after we have already commited some of the response and are no longer in a situation where we can respond appropriately. This seems especially true if we consider each request/response cycle as a single transaction and only flush/commit the data in a filter as we exit the app.

2. commiting data changes we don't want, specifically users being bad with their templates. I suppose our wrapper objects may take care of the potential problem with users making changes to their data that we don't want, but there may also be an issue of app code doing the same thing. It has always seemed ideal to me to only commit changes specifically when needed. If a filter simply commits data at the end of the request/response cycle there is no guarantee that someone hasn't modified an object that is not meant to be saved back to the database.


Like your proposal, the dispatch-level patterns remove begin() and commit() from app code, and we should end up with only a few places that call them.


I like the idea of the dispatch-level pattern much more than our current pattern. After I spend a bit more time on the proposal and get things more flushed out I think I'll have a better idea of which approaches make the most sense.

-- Allen


--a.


Allen Gilliland wrote:

On Thu, 2006-03-09 at 14:21, David M Johnson wrote:
On Mar 9, 2006, at 5:02 PM, Allen Gilliland wrote:
i feel like we are still a little disconnected here. my approach will not alter anything about our ability to use the open session in view pattern. the only difference i see is the difference between these 2 code blocks ...

old way:
begin();
website.store();
commit();

new way:
WeblogManager wmgr = roller.getWeblogManager();
wmgr.saveWebsite(website);

But what if saving a website is just one step in a multi-part transaction? OK, scratch that. If you start by doing the analysis and designing one of the new Manager interfaces, I think we'll have a much better understanding of this proposed refactoring.


i'll just give the short answer now. usually things that are multipart operations are still part of a single logical operation. in fact, we already do this in some places. take a look at the UserManager.createWebsite() method. that is a logical method which wraps a series of persistence operations. so the difference would be ...

old way (in struts action or servlet):
begin();
// setup website
website.store();
// setup template
template.store();
// setup category
category.store();
// etc etc
commit();

new way (logical method in manager class):
UserManager umgr = roller.getUserManager();
umgr.createWebsite(newWebsite, possible, other, data);

when i use the new way i only care wether or not the operation succeeded and i don't have to think about the persistence implications because they are hidden from me. now if it turns out that we have way too many transactions that would need their own logical method in a manager class then maybe this approach won't work, but i believe that in most request/response cycles the operations are pretty isolated and simple. in most of them you are only creating/updating/deleting a single object at a time.

-- Allen


- Dave








Reply via email to