MFlow  Create and maintain dynamic Web applications as easy and fast as console applications
Thou shall not write request handlers
 This release: 11/06/2014.  agocorona@gmail.com (@agocorona) <>< . issues & bugs .  Mflow Source code,     source code of this site




Composable Caching directives

Now the new composable caching policies of MFlow permits the use of aggressive caching policies even for highly dynamic pages, since expressing cache directives is in the hands of the programmer of each particular widget. 


The "separation of concern" as is understood by MVC Web frameworks does not  separate concerns really. A separation of concern forced by  technological problems rather than being separated by components of the problem domain is like if I say that I live better as an slave because I have no need to be concerned by how good or bad the master behave. Caching is an example of this contradiction.


The MVC model forces his own separation of concern and it is inherently non composable. It does not allow to create self contained elements -with M V and C components inside- that can be composed to create applications. That means that it can not separate the concerns in terms of application functionalities: login, payment, content management, lazy loading,  auto-completion widgets etc. As well as other functionalities specific of the individual application. The separation is dictated from below. That is because the technologies involved: event-driven graphical presentation, transactional data management and business logic are not yet fully integrated. See: Towards a deeper integration: A Web language


Concerning caching, A HTTP guru, probably in other department does not know for how long your particular data is valid . Neither you know if the HTTP guru will cache your fresh data in order to avoid loading the server, so you will add a random string to your data to avoid caching. In any case, there is a opportunity lost for optimizing the load of the server without impairing the interaction,


What if you, the programmer of a widget can say to the page: "this widget present data that can be cached for 200 seconds, no matter if it is part of a page or if its data is sent trough Ajax". Or: "the user has logged-in so this page must be private, no matter the policies of the rest of the widgets".

What if the page can get all the cache policies of all the widgets and insert the most restrictive ones in the HTTP header?


Then we would have a true separation of concern without impairing synergies.


If the widget is refreshed using ajax, only the individual cache policy of the widget is taken into account. For example: I´m logged here as editor, so the login widget detect this condition and mark the page as private. But If I click an autorefreshed widget which is not marked as private and have a maxAge directive, the menu options on the left, for example, then the menus can be cached within network proxies and CDNs, so other users can make use of these cached resources without compromising my privacy.


Going this way, more synergies appear in unsuspected ways;  What is the killer appication of the JavasCript frameworks?  To present data sets without repeated accesses to the server using the new HTML5 cache.  Now this can be done by caching AJAX responses in the good old HTTP cache, and make it work in any browser without explicit javascript programming.


Take for example dField:

dField :: (Monad m, FormInput view) => View view m b -> View view m b

Source

Present a widget via AJAX if it is within a witerate context. In the first iteration it present the widget surrounded by a placeholder. subsequent iterations will send just the javascript code necessary for refreshing of the placeholder..

Take for example this code from this page


/noscript/templates/runtimetemplates/listnames

t present data from the server. It uses witerate applied to a widget with four fields that are updated when one of two links are pressed trough AJAX. The placeholders updated are defined by dField. The page as a whole is not updated.


I

 iterateResults allnames len = pageFlow "iter" $ witerate $ do
          NextReg ind <- getSessionData `onNothing` return (NextReg 0)
          dField (getData ind len allnames) <++ br
          dField (getData (ind+1) len allnames) <++ br
          dField (getData (ind+2) len allnames) <++ br
          dField (getData (ind+3) len allnames) <++ br
          setSessionData . NextReg $ next ind len
          r <- wlink True << b << "next" <++ fromStr " "
               <|>
               wlink False << b << "prev"
          when (r== False) $ setSessionData . NextReg $ prev ind len



The HTML code generated is as such:

<div id="p0iter">
   <span id="p1iter">a</span><br>
   <span id="p2iter">b</span><br>
   <span id="p3iter">c</span><br>
   <span id="p4iter">d</span><br>
   <span id="p5iter"><a href="/noscript/templates/runtimetemplates/listnames/itertrue"><b>next</b></a> </span> 
   <span id="p6iter"><a href="/noscript/templates/runtimetemplates/listnames/iterfalse"><b>prev</b></a></span></div>

dField return a page with each new content for the placeholders, that is executed:

setId('p1iter','e');
setId('p2iter','f');
setId('p4iter','h');
setId('p3iter','g');

However each click generates a request to the server. Even if the request has been served already. We can cache the javascript responses using the HTTP cache, so the above javascript code can be recovered locally when the user press the prev link.

But if we tailor the caching policy for this widget behaviour, maybe we can not mix it with other widgets that demand a more strict or relaxed policy. We can not do it in a fully composable way if we rely on the raw HTTP headers. We need to fuse the caching policy inside the building block, the widget. Then each widget will express its caching needs in the page and later a caching compiler will decide which caching policy is adequate for all the widgets in the page. 

For example, if for some reasons this widget is in a page with another widget that demand no caching at all, the compiler will obey this more restrictive policy. This widget will not benefit from caching but it will work well. Otherwise, the caching demand  will be honored and the data presentation will work as optimally for the server load as a javascript framework with  HTML5 cache would do, with the advantage that we use out well tested HTTP cache.

Since this widget uses Ajax, his caching policy will not interfere with the other widgets. Only when the whole page is sent, in the first invocation or  when Javascript is disabled, the whole page will be sent and the policies will be mixed.

Additionally each chunk of responses will need to have its own distinctive link. If you look at the HTML generated in the original code, the URLs are two for all the chunks: true and false. Now we need to generate the link names dinamically, two different for each chunk since the HTTP cache distinguish each entry  in the cache by the link URL. 

How? with dField: Here the two links have been enclosed in dField elements and now they return a dynamic content: the index of the next and previous set to present. Additionally a HTTP caching element has been added: They are HTTP cache control directives in the HTTP header, but in MFlow they are composable in the way I have mentioned above.

In this case, maxAge means that the content can be stored in the cache for 300 seconds. look at the MFlow.Forms.Cache module for the different cache control directives.

iterateResults allnames len = pageFlow "iter" . witerate $ do
          maxAge 300
          NextReg ind <- getSessionData `onNothing` return (NextReg 0)
          dField (getData ind len allnames) <++ br
          dField (getData (ind+1) len allnames) <++ br
          dField (getData (ind+2) len allnames) <++ br
          dField (getData (ind+3) len allnames) <++ br
          r <- dField(wlink (next ind len) << b << "next" <++ fromStr " ")
               <|>
               dField(wlink (prev ind len) << b << "prev")
<|> restp
          setSessionData . NextReg $ r

But what does restp? It reads a rest path segment. The links "next" and "prev" expect two fixed numbers in the rest param corresponding to the next and previous index to be presented. 

The links generated, analogous to the HTML code presented above, now is dynamic and has this schema:

"/noscript/templates/runtimetemplates/listnames/<next index>"
"/noscript/templates/runtimetemplates/listnames/<previous index>">

If the indexes do not match with what the code is expecting, they fail. restp gets any index, since the request can come from a cached presentation that has been loaded in the browser and has been sent to the server, and this index can be whatever.

The javascript generated in each iteration is cached in the browser's HTTP cache for 300 seconds. Not the javascript has two new entries corresponding to the dynamic links:

setId('p1iter','e');
setId('p2iter','f');
setId('p4iter','h');
setId('p3iter','g');
ef="/noscript/templates/runtimetemplates/listnames/iter8"><b>next</b></a> '); setId('p6iter','<a h
setId('p5iter','<a h
rref="/noscript/templates/runtimetemplates/listnames/iter0"><b>prev</b></a>');



comments powered by Disqus