cocoon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Stefano Mazzocchi <stef...@apache.org>
Subject Re: [RT] Managing Flow and Resources
Date Tue, 11 Dec 2001 14:03:54 GMT
Ovidiu Predescu wrote:
> 
> On Mon, 10 Dec 2001 10:56:39 -0500, Berin Loritsch <bloritsch@apache.org> wrote:
> 
> > Berin Loritsch wrote:
> >
> > > Torsten Curdt wrote:
> > >
> >
> > >>> To be able to incorporate URLs to older points in the processing, the
> > >>> send-response() function could return as value an object that contains
> > >>> the URL to its continuation. You can pass this URL in the optional
> > >>> dictionary, as an additional argument, if you wish so.
> > >>>
> > >>> function addNumbers(HttpRequest request)
> > >>> {
> > >>>  send-response("first-page.xml", "my-pipeline");
> > >>>  first = request.getIntParameter("first");
> > >>>  send-response("second-page.xml", "my-pipeline", {"first" = first});
> > >>>  second = request.getIntParameter("second");
> > >>>  result = first + second;
> > >>>  send-response("third-page.xml", "my-pipeline", {"result" = result});
> > >>> }
> >
> >
> > Having looked at this example, something doesn't sit right with me.  This
> > has to do with the fundamental URL contracts.  This "flow" is managed within
> > one URL.  For instance, it could be mounted at "http://foo.com/add".  It
> > would behave like this:
> >
> > URL: http://foo.com/add
> > PAGE:
> >
> >      Enter First Number: _________
> >      [Submit]
> >
> >
> >
> > URL: http://foo.com/add
> > PAGE:
> >
> >      Enter Second Number: _________
> >      [Submit]
> >
> >
> >
> > URL: http://foo.com/add
> > PAGE:
> >
> >      The result is: _________
> >      [Done]
> 
> Because of the continuations, the URLs for the second and third pages
> would probably look more like:
> 
> http://foo.com/add/4ffds8454mfd90
> 
> with the last part identifying the continuation object.


> This is similar with the way WebObjects does it. I'm open to
> suggestions though on a better scheme to encode the continuation
> identifier.

I was thinking about some more informatively looking URI such as

 http://foo.com/add(4ffds8454mfd90)

or

 http://foo.com/add[4ffds8454mfd90]

but Mozilla URL-encodes the square brakets and turnes it into %xx syntax
which ruins the entire concept.

I like this more than appending a slash because, in fact, we are dealing
with the same resource of before, we are just using a different entry
point.

Another solution is faking anchors

 http://foo.com/add#4ffds8454mfd90

but looks less appealing to me.

Of course, when we have the ability to POST data, we should use
alternatively an hidden field in the form.
 
> > While this is a possibility, and might be helpful when you only want one
> > entry point for a form process, it does not address options when you have
> > multiple entry points.  Nor does it address content to url issues.  If we
> > want the flow to be managed externally, we must have a method to either
> > plant the target in the pipeline, or use redirects.  Niether are very clean.
> >
> > In the first approach (embedding targets in the pipeline) we would
> > have to add a <parameter name="target" value="second-page.html"/> to
> > embed in the form.  I find this to be acceptable when you don't want
> > to depend on the redirection mechanism in a server--or when it is
> > well known what the next page is going to be.  This is in cases
> > where there is only one "next page".
> >
> > In the second approach, you must depend on the servers
> > implementation of redirects (while supposedly standard, I have run
> > into some issues).  There are also different type of redirects (a
> > host of 30X HTTP response messages).  None of them really supports
> > the notion of "normal" navigational flow between form pages--though
> > _many_ systems (like ColdFusion) routinely use them to manage form
> > flow.
> >
> > For instance, with ColdFusion, it is concidered good practice to
> > have two templates per form page.  The first template is to display
> > the form, and the second to simply process it.  When the second is
> > done, it simply redirects the user to the next page.  I don't like
> > this because it artificially polutes the URI space with intermediate
> > targets who's sole responsibility is to point you to another
> > resource.
> >
> > In Cocoon the only way to manage the URI space as well as processing
> > is to use redirects and value substitution.  For instance, I would
> > much rather have a form explicitly say it's next page if it does not
> > depend on user input like this:
> >
> > <form action="{target}"/>
> >
> > The target is chosen by the flow manager, and given to the form so
> > no redirection has to take place.  This is better IMO than forcing
> > the use of redirects for these simple cases.
> >
> > What about multiple targets from one form?  In those cases, it is
> > much more desirable to use redirections after we service the
> > request.  Using the psuedo-markup I expressed earlier the concepts
> > could be stated like this:
> >
> > <functions>
> >    <function name="addNumbers" pipeline="my-pipeline">
> >      <send-response page="first-page.xml">
> >        <parameter name="target" value="second-page.html"/>
> >      </send-response>
> >      <call-action name="getFirst"/>
> >      <validate>
> >        <on-error>
> >          <redirect-to "first-page.html"/>
> >        </on-error>
> >      </validate>
> >      <send-response page="second-page.xml">
> >        <parameter name="first" value="{first}"/>
> >        <parameter name="target" value="third-page.html"/>
> >      </send-response>
> >      <call-action name="getSecondResult"/>
> >      <send-response page="third-page.xml">
> >        <parameter name="result" value="{result}"/>
> >      </send-response>
> >    </function>
> > </functions>
> >
> > This is not necessarily clean looking at it though.
> 
> If you have a form with multiple entry points, you just split the
> function that handles the navigation in multiple functions:
> 
> function first-page(request)
> {
>   send-response("first-page.xml", "my-pipeline");
>   second-page(request);
> }
> 
> function second-page(request)
> {
>   send-response("second-page.xml", "my-pipeline");
>   third-page(request);
> }
> 
> function third-page(request)
> {
>   send-response("third-page.xml", "my-pipeline");
> }
> 
> You can then mount each function at different URLs:
> 
> http://foo.com/first/
> 
>         serves first-page.xml
> 
> http://foo.com/second/
> 
>         serves second-page.xml
> 
> http://foo.com/third/
> 
>         serves third-page.xml
> 
> If a user starts filling in the form at the first page, he/she will
> see the following sequence of URLs:
> 
> http://foo.com/first            for the first page
> http://foo.com/first/adb43432   for the second page
> http://foo.com/first/432jfw9    for the third page
> 
> This is because the navigation started from the first-page() function,
> and logically all the computation starts as a result of the initial
> invocation of the first-page() function.
> 
> If instead the user starts from the second page in the form, he/she
> will see the following sequence:
> 
> http://foo.com/second           for the second page
> http://foo.com/second/4312ufd8  for the third page
> 
> This is the similar with the first navigation I described above,
> except that the entry point in the computation was the second-page()
> function.
> 
> I would say that this approach handles both the issues you mentioned
> above, the multiple entry points in a form, and the content to URL
> mapping.
> 
> When you have forms whose result can branch the flow, you can use the
> control flow statements, like "if", "while" etc. Here's a non-trivial
> example of continuations at work.
> 
> function shopping-cart()
> {
>   set-default-pipeline("my-pipeline");
>   send-page("show-shopping-cart.xml");
>   operation = request.getParameter("operation");
>   if (operation == "continue-shopping")
>     send-page("continue-shopping");
>   else if (operation == "buy")
>     buy();
>   else if (operation == "update-quantities")
>     update-quantities();
> }
> 
> function buy()
> {
>   credentials = get-user();
>   ship-to-address(credentials);
>   charge-credit-card(credentials);
> }
> 
> function get-user()
> {
>   // get-user() returns only with valid credentials.
>   // If no valid credentials are found, this function never returns.
> 
>   for (count = 0; count < 3; count++) {
>     send-page("get-username.xml");
> 
>     user = request.getParameter("username");
>     passwd = request.getParameter("password");
> 
>     usersDB = UsersDatabase.newConnection();
>     if (!usersDB.isUserKnown(user, passwd))
>       registration();
>     else
>       return userDB.credentialsForUser(user, passwd);
>   }
> 
>   // This shows the user a sorry page with a single link that points to Home.
>   // This is an example of a non-local exit, where the computation will
>   // continue at a different location in the program.
>   send-page("sorry-page.xml");
> }
> 
> function ship-to-address(credentials)
> {
>   send-page("ship-to-address", "my-pipeline",
>             {"address" = credentials.getAddress()});
>   change = request.getParameter("change");
>   if (change)
>     return change-address(credentials);
>   else
>     return credentials.getAddress();
> }
> 
> function registration()
> {
>   // Register a new user
>   send-page("register.xml");
> 
>   while (true) {
>     username = request.getParameter("username");
>     if (usersDB.isUserKnown(username))
>       send-page("user-already-known.xml", {"username" = username});
>     else
>       break;
>   }
> 
>   passwd = request.getParameter("password");
>   usersDB.registerNewUser(username, password);
> 
>   send-page("registration-successful.xml"});
> }
> 
> function change-address(credentials)
> {
>   // Function to change the address. Returns the address the user chose
>   ...
> }
> 
> function charge-credit-card(credentials)
> {
>   // Charge the credit card of the user
>   ...
> }
> 
> function update-quantities()
> {
>   ...
> }
> 
> This example shows how Java objects are used to perform critical
> functions like connecting to the user's databases, checking for new
> users and registering them.
> 
> Also look closely at get-user() and registration() functions. The idea
> is to present the user with the option of registering himself/herself
> if they don't have an account. After the user registers, you want the
> processing to continue where it was interrupted, e.g. you want the
> registration() function to return to get-user(), right after the call
> to registration(). Since the send-page() function passes to the
> processing pipeline in the environment the URL to its continuation, by
> clicking on that link the user effectively returns from registration()
> back into get-user().
> 
> Another scenario is when you don't want the user to return to the
> current computation. In that case you put links in the generated pages
> that point back to other functions. They will be invoked through the
> sitemap as usual, starting a new stack frame on the server side.
> 
> Notice how easy is to handle errors with this approach. In get-user()
> for example, the user is asked to login into the system. If the user
> isn't registered yet, he/she can choose to register. If the user
> doesn't successfully register, he/she is sent to a page where he/she
> cannot return in the flow of the program (the user can always use the
> "Back" button to go back and complete the registration
> successfully). There is no need in the buy() function to handle an
> unsuccessful registration.

Yes, I had the taste of the power of continuations when I dived more
into the paper you presented (and implemented myself a few scheme
examples to get the feeling of it).

I think we *do* have very interesting functionality here that would
finally remove the obstacles the stateless web model forced us on.

I do see, now, why a FSM approach is probably not the best approach.

> > The concept of "continuations" while a flexible and powerful concept must
> > come to grips with the concept of branching flows--which I do not think
> > it does.
> >
> > I think that full FSM terminology is overkill, and would only serve to
> > frighten newbies.  However, digging through the archives, I came up with
> > a concept that would also work:
> >
> > <flow name="addNumbers" start="first-page">
> >    <resource name="first-page">
> >      <pipeline type="my-pipeline" source="first-page.xml"/>
> >      <next name="second-page"/>
> >    </resource>
> >    <resource name="second-page">
> >      <action type="getFirst">
> >        <pipeline type="my-pipeline" source="second-page.xml"/>
> >        <next name="third-page"/>
> >      </action>
> >      <redirect-to resource="first-page"/>
> >    </resource>
> >    <resource name="third-page">
> >      <action type="getSecondResult">
> >        <pipeline type="my-pipeline" source="third-page.xml"/>
> >        <done/>
> >      </action>
> >      <redirect-to resource="second-page"/>
> >    </resource>
> > </flow>
> >
> > This uses the same semantics for actions and provides the transitions,
> > handling errors.
> 
> I don't like the above semantic. The redirect functionality was
> invented to alleviate the lack of continuing an interrupted processing
> that can change the path in your program. I think there's no need for
> it with the model I propose. Maybe I'm wrong, please show me if this
> is the case.

I find myself resonating with Ovidiu on this.

The whole argument reminds me of the cognitive friction I had when
abandoning BASIC GOTOs switching to Pascal. 

If this is so, the "GOTO considered harmful" decade-old anti-pattern
could be well applied to web programming and explicit flow redirection,
giving us a 'clear' metric to judge a web technology.

In this case, Ovidiu's proposal is clearly better than Berin's.

> Also I don't like to express logic in XML, it's not what it was
> designed for. We need a programming language, so why use an XML syntax
> to express this? The attempts to use XML to implement a programming
> language are taking XML too far, where it wasn't designed to work.

Granted.

In fact, for flow-driving semantics, I'd far prefer a ECMAScript-like
syntax because it would be

 a) less verbose than XML
 b) generally more readable (consider escaping <,>,& or using CDATA)
 c) easier to learn/use/understand for C/C++/C#/Java/JavaScript people 
   (a very big percentage of the web tech population nowadays)

At the same time, for global site-mapping semantics, the XML syntax is
still the way to go.

> --
> 
> I think with the new model, there's no need for actions, redirects and
> other things that the sitemap currently has.

Yes, I've always expressed my feeling that Actions were hacks.

> They are no longer needed
> as you have full control over what's going on in your application at
> the program level. And you can control your application in a much
> better way than before.

Absolutely.
 
> In the new model, the sitemap becomes a dispatcher to either a
> pipeline, or to a function in our language with implicit continuations
> (any good name for it?).

I think you are oversimplifying: I agree with you that actions and
action-sets are something that should not be in the sitemap (I always
found strident the different degree of reuse you had with pipeline
components and actions), but at the same time, the current sitemap
semantics is IMO very well designed for stateless publishing needs,
which still are the great majority of the web resources.

> A pipeline describes how an input document should be transformed to
> HTML/WML etc, and nothing else. No actions, no redirects, no
> programming at the pipeline level.

In fact, the name "sitemap" is exactly that: 'a map for the site'.

When Pier and I designed the concept, we had the feeling that it was
possible to separate the concer of 'mapping resources' and assembling
the pipelines that generate them, from that of programming the flow
between the different resources.

Since we couldn't find an elegant way to add these capabilities (at that
time, we were thinking about letting Turbine take care of that part!) we
decided to postpone further design.

Unfortunately, since flow-driving instructions are required even for the
most simple restriction-based publishing system, the need for headless
programmatic activity emerged, and the Turbine Action concept was ported
over to Cocoon.

Giacomo knows that I never really liked this approach, but I didn't stop
the effort because I wasn't able to provide a more elegant solution.

Interesting enough, what you described in your above example is exactly
the kind of 'flowmap' that I've been looking for in order to move away
actions from the sitemap.

I knew that the intrinsic 'wait for request' behavior of the site forces
the sitemap to follow a declarative approach, as much as the 'drive the
flow' behavior of a web application would force the flowmap to follow a
more procedural approach.

Even more: since the sitemap might be written by people used to publish
information and documents, the XML syntax is more suited for their
skillset. At the same time, since the flowmap is written by people used
to write programming languages, a java-like syntax is better suited for
their skillset.

In this vision, Berin's proposal to make pipelines reusable by adding
variable substitution might allow both sitemap and flowmap to use sort
these pipeline definitions (pipemaps?) and reduce overall verbosity.

Anyway, I think that by trying to prove the Flowmap concept wrong,
Ovidiu gave us the best example of a flowmap in terms of syntax
(code-based instead of XML-based) and functionality (continuations-based
instead of FSM-based).
 
> If you need to describe logic, you do it using the language. This
> describes which pages are sent in what order. When you want to
> generate a result, you call send-page() with the source document, a
> pipeline definition that should process your document, and additional
> data to be used in generating the source page dynamically.
> 
> To generate the source document, it's better if we use a markup
> language that prevents users from doing computations in the page. The
> page should be generated only from data passed to the pipeline by
> send-page(). I haven't used it before, but Velocity seems to be a good
> fit for this, as it doesn't allow any embedded processing in the page.

Since Cocoon content will always be XML-ized, I see two possible
solutions here:
 
 1) use XML-orthogonal solutions (Velocity)

<p>Today is ${date}</p>

 2) use XML-namespaced solutions

<p>Today is <dxml:date/></p>

Again, the semantics are exactly the same, but the first approach if
more friendly to code writers, the second to HTML writers. We could even
have both and let the user decide which one he/she like the best.

> With this model we have the clear separation of concerns. Each layer
> does only the things it's supposed to do, in a clean way.

Absolutely:

 sitemap -> handles the stateless needs
 flowmap -> handles the statefull needs

Add a simple and effective way to pass data from the *maps to the
pipelines and into the content XML files and we are done.

-- 
Stefano Mazzocchi      One must still have chaos in oneself to be
                          able to give birth to a dancing star.
<stefano@apache.org>                             Friedrich Nietzsche
--------------------------------------------------------------------



---------------------------------------------------------------------
To unsubscribe, e-mail: cocoon-dev-unsubscribe@xml.apache.org
For additional commands, email: cocoon-dev-help@xml.apache.org


Mime
View raw message