Allen:
What you've got is not the pattern I had in mind, but I can't say for sure
whether it is good or not. It is not a pattern I'm familiar with at all, so
I'm not sure.
I've used Spring's basic dependency injection/bean factory support, but
never used Spring for transaction demarcation, so I don't have first-hand
experience; I take it Matt does, and likes it. He says he's biased, but if
that's just the bias of prior experience, I don't think such bias needs to
be discounted. Roller is not characteristically different in structure from
the typical webapp, and the typical webapp built with AppFuse, so there's
probably something to be noted.
I'm biased by my own experience. I've used the pattern below before and
I'm comfortable with it. I've described it in pieces but not as a whole, so
I'll try to do that here (see below). The only thing I really feel strongly
about is that we should adopt a conventional pattern and not invent here. I
feel the pattern below is conventional; described here by me, but not
invented by me. To me, the pattern in the new backend branch feels mostly
invented and feels ad hoc.
The primary metric for "simplicity" I would use here is: a smaller number of
places where the code knows about and deals with Hibernate Sessions or
transactions at all. I think the pattern in the new backend branch is not
as good along this metric. I think the pattern below is simpler and
cleaner.
Matt's suggestion of using Spring's declarative (I'm assuming) demarcation
also follows an established convention, and achieves similar or greater
simplicity in this metric than my proposed pattern. There are even fewer
places that know about the Session or Transaction at all. More does have
to be expressed in configuration though. I really like the parts of Spring
I have used, and I'd like to use more of Spring in Roller, but I still think
we should defer a major jump to Spring for a major architectural revision of
Roller. I think the pattern below is a good intermediate step. I can see
going from the pattern below to Spring later.
What's similar about the pattern below and the one in the new backend branch
is that there is a flush/commit between model updating operations and the
view. That sounds like what you're referring to.
The similarity pretty much ends there though. The pattern below has no
transaction-aware code in the model layer code itself and, with proper class
structure, it has only a few places in the entire codebase where there is
any transaction-aware code at all. Most of the code in both app and model
layers knows nothing about the Hibernate Session or transaction demarcation;
it expects it but is not explicitly cognizant of it.
With the right base classes, this pattern should have very few places in the
code that know about Sessions and transactions. I'm talking about a total
of something like 4 or 5 places, plus a few exceptional background tasks
that need to manage multiple transactions per execution on their own.
--a.
---------------------
Here is an attempt at a reasonably complete description of the pattern (not
my own pattern):
The pattern involves explicit code in the following six aspects, which with
proper class structure occur at only a few cut points in the codebase:
(1) Have a ServletFilter create the Hibernate Session and begin the initial
Transaction before chaining. Associate the Session with the thread context.
In a finally clause, the filter will always rollback the transaction if it
has not already been committed or rolled back. The filter expects the app
layer to handle commits on success.
(2) The model layer does nothing at all about transaction demarcation. It
always assumes a transaction. You see no begin or commit code in the model
layer. No explicit cognizance of Session or transaction.
(3) For view web app operations, the app layer also does nothing at all
about transaction management. It always uses the current session and
transaction. No explicit cognizance of Session or transaction.
(4) For model-updating web app operations, the app layer gets the Session
and does a flush and commit at successful completion, just before forwarding
to the normal success view. After committing, a new transaction is started
immediately by that same code. On exception/error, the app layer should do
a rollback; it could propagate the exception, but typically it needs to
return a meanngful error response view to the user. Explicit cognizance of
the Session and transaction is required in this location, but I think this
can be limited to one cut point in the code if the action classes are
structured properly. This code could be isolated in one base class that is
used for all model-updating web app actions and encapsulates logic to commit
and forwards to a regular view in the success case or rollback and forward
to an error view for the error/exception case.
(5) For web service operations the servlet filter is still used, but a
flush/commit (and rollback for exception cases) is needed in the service()
method of the entry point servlet. You generally need to return an error
response to the caller but also rollback. For Axis-based web services, this
is typically done in extension of AxisServlet. In Roller there is
RollerXMLRPCServlet. If there are other similar entry point servlets in
Roller, they'd be needed there.
(6) For background tasks, a base class for normal background tasks can do
the same thing that is done in the filter (in 1). Normal means only one
transaction is needed per execution. Some background task operations will
involve processing a potentially long list of individual objects, and a
problem with processing one object should not affect others. In this
pattern, the Hibernate Session/transaction management required for this is
done by the specific task code. The only place I can think of where Roller
might need this is during ping queue processing, where each ping is handled
separately. There might be something similar in the feed processing task in
Planet, where one probably wants to handle each feed and possibly each entry
from the feed individually.
----- Original Message -----
From: "Matt Raible" <[EMAIL PROTECTED]>
To: <[email protected]>
Sent: Friday, April 14, 2006 11:53 PM
Subject: Re: new backend status and questions
Simplicity is king in my book. I still think there's a fair amount of
value in using Spring to define the transaction attributes for the
middle-tier. Of course, I'm biased. ;-)
Matt
On 4/14/06, Allen Gilliland <[EMAIL PROTECTED]> wrote:
okay, the status on the backend refactorings is pretty good and i've got
all the major functionality fixed up and unit tested. i've also been
testing things out from the GUI along the way to make sure the
presentation layer was working as well. everything should be working
except for folders/bookmarks and possibly some referers stuff.
now for the more important part ...
as i have been reworking things i have slowly been warming up to Anil's
constant pleas that we provide some sort of transaction demarcation
available outside of the business layer. i still believe strongly that
we should attempt to hide all knowledge of persistence actions from the
presentation layer, however, based on the way that Hibernate wants us to
do things I think that providing a simple flush method may be more
appropriate than the strict boundaries that i originally proposed.
while this will require me to rollback a couple of the changes i have
made i still think that most of the work is applicable, notably that we
have removed the PersistenceStrategy and given more freedom to
persistence implementors, removed persistence specific methods from our
domain model (save() and remove()), and that we have removed the
extraneous transaction methods (begin(), setUser(), getUser(), and
rollback()) from being accessible to the presentation layer.
so, what i would propose is that we add a new method to the Roller
interface, flush(), which will trigger a database flush for any changes
that have happened within the current running transaction. all other
transaction operations (begin() and rollback()) are handled behind the
scenes by the persistence implementation. transactions are
automatically started when a Session (i.e. Request) begins. after any
flush occurs a new transaction begins to prepare for further changes.
if there is ever a problem caused by a flush then a rollback happens
automatically. so the work flow would look like this ...
incoming request
session begins, transaction begins
objects are queried for and updated
flush()
-- optionally make more changes and flush() again
response committed
at the end of the day this model places a greater emphasis on having a
properly structured domain model, which i believe is a good thing.
so, thoughts anyone? Anil, does this sound more like what you had in
mind?
-- Allen