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