Hmm...well there are three main issues we are discussing here, although you
mention at the end they do have some overlap:
1. Framework-level request processing
2. Action-level processing
3. Application-level page flow/workflow
When was referring to commons-chain being a poor match for workflow I was
primarily talking about #1. Examples of this include how Struts Classic
commands have all sorts of ugly logic in them to skip themselves if an object
isn't found in the context. This seems fine until you step back and realize it
is very hard to see the overall process flow without digging into the source
code of each command. I'm not sure I have a solution to this problem so at the
moment, Ti uses commons-chain so that at least a user could override a step or
two in the process as needed.
Action-level processing is what I would consider basic services available for
action invocation and view processing, including form bean creation, population,
validation, logging, etc. Interceptors is a good design, I feel, for this
problem because you usually want a short, linear process/chain that may differ
per action, but generally be the same. There is no need for branching or states
here, as this could be considered a subset of Aspect-Oriented Programming.
Application-level page flow targets a different audience and presumably harder
problems including multi-request flows, and I would classify them as solving the
problem of what action or actions should be invoked per request. I would
include action and view processing in this flow. Solutions include:
1. Beehive's Page Flow
2. Spring's Web Flow
3. Cocoon's Control Flow
What I like about Beehive's Page Flow is it is annotation driven, and supports
advanced features like shared flows and inherited flows. Spring's Web Flow is
new to me, so I can't comment much other than it seems clean but difficult to
follow without a visualization tool. And for Cocoon and continuations in
general, I still think they are a great solution since the workflow is
implemented in code (Javascript, Java, Ruby, whatever), and furthermore, the
workflow is in one, easy to read location, generally one method/function.
I suppose it could be argued that all these could share the same tool, but, IMO,
each have their own requirements and deserve a specialized tool suited for their
use cases. Commons-chain could, perhaps, be used for these three types of
processing, but I fear it would be verbose and cumbersome.
Don
Craig McClanahan wrote:
On 9/1/05, Don Brown <[EMAIL PROTECTED]> wrote:
Therefore, my point is an interceptor chain is better suited to a scalable,
linear process flow, while chain is better for decision points. And
neither,
I'd argue, is well suited for a robust, configurable workflow, and this
surely
we can agree we are seeing with commons-chain in Struts Classic.
I generally agree with this, and the other points Don has made in this
thread ... and would suggest (sorry Don :-) that the same issue happens with
continuations based architectures like Cocoon Webflow and Struts Flow. None
of these approaches seem to deal with conditional branches in workflows very
well.
This is one of the reasons that I was initially enamored with Spring
WebFlow's approach, which defines the processing logic as a series of
states, linked by transitions. States can be action states (like calling
Action.execute() in a Struts app) or view states (display a page, receive
the subsequent form submit. All states return an outcome that can be used to
drive the transition, so you can do branches very easily. Or, you can glue
together any number of action states in sequence to get the fine grained
sequential functionality that a chain provides.
In Shale, this idea is encapsulated as a "dialog", which leverages the fact
that JSF action methods already returned a String outcome (so that it fit
naturally into the state transition model) -- I just added the idea of an
action state represented as an expression that called some arbitrary method
that also returned a String. This lets you do things like representing an
entire workflow in an easy to understand configuration (or, equivalently, in
a UML state diagram):
<!-- Log On / Create Profile Dialog -->
<dialog name="Log On"
start="Check Cookie">
<action name="Check Cookie"
method="#{profile$logon.check}">
<transition outcome="authenticated"
target="Exit"/>
<transition outcome="unauthenticated"
target="Logon Form"/>
</action>
<view name="Logon Form"
viewId="/profile/logon.jsp">
<transition outcome="authenticated"
target="Exit"/>
<transition outcome="create"
target="Create Profile"/>
</view>
<subdialog name="Create Profile"
dialogName="Edit Profile">
<transition outcome="success"
target="Exit"/>
</subdialog>
<end name="Exit"
viewId="/usecases.jsp"/>
</dialog>
Although the dialog facility is primary focused around multi-request
workflows, you can leverage the same concepts for fine grained flows within
a particular request.
Craig
Don
BTW, I'm really enjoying this discussion and have missed these on this
list.
Ted Husted wrote:
On 9/1/05, Don Brown <[EMAIL PROTECTED]> wrote:
In that case, I find interceptors more practical, as they allow
you to have code before and after processing that uses method variables.
With
Chain, you have to use a Filter and even then, there is no way to share
variables between the two blocks of code without instance variables
which has
its own problems.
First, you're doing the work, Don, and so you're welcome to make the
decisions :)
Though, I don't understand is why you'd want to be restricted to two
blocks of code :)
With Chain, any number of blocks of code, be they commands or chains,
in any combination, can be the object of the request processing.
In OverDrive/Nexus, we do find having interceptors that surround each
request useful. It's not hard to define "pre" and "post" chains, and
then at runtime create a third chain to execute them all.
public void ExecuteView (IRequestContext context)
{
IRequestCommand command = VerifyRequest (context);
if (context.IsNominal)
{
IChain chain = new Chain ();
if (_PreOp!=null) chain.AddCommand (_PreOp);
chain.AddCommand (command);
if (_PostOp!=null) chain.AddCommand (_PostOp);
try
{
chain.Execute (context);
}
catch (Exception e)
{
context.Fault = e;
}
}
}
http://svn.apache.org/viewcvs.cgi/struts/sandbox/trunk/overdrive/Nexus/Extras/Spring/Catalog.cs?view=markup
The PreOp and PostOp chains are defined in the configuration, along
with everything else.
But, we're not trying to solve the problems of navigational workflows,
only the problem of processing business use cases and interacting with
a presentation layer
-Ted.,
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]