Wednesday, April 17, 2013

Controlling backtracking in MFlow

MFlow Web applications are defined like console applications, as sequences of interactions with the user. When the user press the back button and send a request from a previous page, the application backtrack to the statement that match the request, so that the application resume from this point on. That way, the state is synchronized with the user navigation. This may be the skeleton of a flow with a shopping page that manage a shopping cart


shop products= do 
   setHeader $ body . html
   setTimeouts 120 (30*24*60*60) 
   loop emptyCart 
   where 
   loop cart= do 
      r <- step . ask $ products <** showCart cart 
                      <+> wlink () << p << exit shop case r of 
      case r of 
       (Just bought,_) -> loop cart 
       _               -> breturn cart


This flow has a single ask statement, but it is within a loop and step make this result persistent. Let's see what this means. setTimeouts establishes how long the process is running in memory and how long the serialized session data is recorded, respectively. Each product selected on each ask request is stored in the log. If, after two minutes, the user select another product, the process will be restarted and will recover the shopping cart state by re-executing the loop, taking as input the log content until the log is finished. Unless the user does not enter for a month, in which case, the log will be deleted and the shopping cart will appear empty. When the user press the "exit shop" link, the flow will return the shop cart to the calling flow. In the previous example, it is noteworthy that, if the user is adding products to the shopping cart, when he press the back button, the previous page will appear, with one product less in the cart. In this page, when he select other product and send the request, the application will backtrack one step in the loop, so the shopping cart will roll back from the last transaction and the shopping cart will reflect the page that the user is seeing. This synchronization of state and pages occurs on every case thanks to the backtracking mechanism. So that if the user is in a shopping cart and go back, he see pages with less and less items. When the user decides to proceed from this point the state of the shopping cart -because of the backtracking- will match "magically" with what is displayed in the user page.

All this is fine, but sometimes it is dangerous to go back. For example when a transaction that can not be undone has been done. For example a payment. For this purpose, it is necessary a cut in backtracking.

 I added preventGoingBack, and pushed it to the Git repository. It perform this cut in backtracking when going back in the flow. When this condition is detected, ir executes a subflow that is the parameter. Usually this subflow consist of a single ask statement that present a single page with an error. When going forward, the statement is transparent:

    ask $ wlink () << b << "press here to pay 100000 $ "
    payIt
    preventGoingBack . ask $ b << "You payed 10000 once" ++> wlink () << b << " Please press here"
    ask $ wlink () << b << "Press the back button to verify that you can not pay again" 
    where
    payIt= liftIO $ print "paying"

Here preventGoingBack does not permit to navigate back to code before himself. The user can go back in the browser, but what the user will see when he press a button or link in the flow, is the message of preventGoingBack and their available options, after which the flow will proceed normally.

This example uses the last version of MFlow at https://github.com/agocorona/MFlow.

It uses the last version of Workflow https://github.com/agocorona/Workflow

TCache : https://github.com/agocorona/TCache

RefSerialize: https://github.com/agocorona/RefSerialize

No comments: