Apache Cocoon – Control Flow

La mancanza di stato

Quando si sviluppa una applicazione web, si deve fare i conti con una caratteristica del protocollo HTTP: la mancanza di stato. Non c’è nessuna informazione sulla storia delle request/response intercorse tra un
client ed un server. Questo rende problematico lo sviluppo di applicazioni web complesse.

Da un lato la mancanza di stato garantisce un’ottima scalabilità dell’applicazione,
poichè non c’è la necessità di mantenere un servizio o una servlet
per ogni utente. D’altro canto l’onere di gestire lo stato della conversazione
passa tutto sullo spalle dello sviluppatore.

Si può pensare ad una applicazione
web come una coppia di request/response come nella figura seguente:

Modello request/response

Con questo modello il controllo sta tutto nel browser. L’applicazione si
attiva solo in corrispondenza di richieste fatte dal browser. Questo modello
funziona bene se le richieste sono piccole e indipendenti, ma entra in crisi
in contesti complessi. Basta pensare ad un carrello elettronico, se si fa un
doppio click sul pulsante di invio del form, troveremo due oggetti identici nel
nostro carrello.

Continuation

L’idea alla base della continuation è la seguente: ad ogni richiesta del client,
lasciare al framework il compito di caricare e salvare lo stato, all’inizio e alla fine di ogni richiesta.

Cocoon mette a disposizione tre implementazioni della continuation:
una in JavaScript basata su Rhino JavaScript di Mozilla detta
flowscript, due in Java: JavaFlow e l’Apples Flow.
Per capire come funziona la continuazione vediamo subito
un esempio in JavaFlow:

  1. import org.apache.cocoon.components. flow. java.AbstractContinuable;
  2. import org.apache.cocoon.components. flow. java.VarMap;
  3. public class CalculatorFlow extends AbstractContinuable {
  4. public void doCalculator() {
  5. sendPageAndWait(“getNumberA.html”) ;
  6. float a = Float .parseFloat(getRequest() .getParameter(“a”)) ;
  7. sendPageAndWait(“getNumberB.html”) ;
  8. float b = Float .parseFloat(getRequest() .getParameter(“a”)) ;
  9. sendPageAndWait(“getOperator.html”) ;
  10. String op = getRequest() .getParameter(“operator”) ;
  11. i f (op. equals(“plus”)) {
  12. sendPage(“page/calculator?result”, new VarMap() .add(“result”, a + b))
  13. ;
  14. } else i f (op. equals(“minus”)) {
  15. sendPage(“page/calculator?result”, new VarMap() .add(“result”, a ? b))
  16. ;
  17. } else i f (op. equals(“multiply”)) {
  18. sendPage(“page/calculator?result”, new VarMap() .add(“result”, a  b))
  19. ;
  20. } else i f (op. equals(“divide”)) {
  21. i f (b == 0f )
  22. sendPageAndWait(“page/calculator?message”, new VarMap() .add(“
  23. message”, “Error: Cannot divide by zero!”)) ;
  24. sendPage(“page/calculator?result”, new VarMap() .add(“result”, a / b))
  25. ;
  26. } else {
  27. sendPageAndWait(“page/calculator?message”, new VarMap() .add(“message”
  28. , “Error: Unkown operator !”)) ;
  29. }
  30. }
  31. }

Quando un client richiede l’applicazione calcolatrice viene avviata la funzione
doCalculator. Alla linea 5 del listato c’è la funzione speciale
sendPageAndWait; questa funzione è in grado di invocare pagine web,
selezionado una pipeline, e di passare dei parametri, una mappa di parametri
come si vede alla riga 15 dove c’è l’altra funzione speciale sendPage. Quando
la funzione sendPageAndWait invia una pagina, viene bloccata l’esecuzione
del thread fino a quando l’utente cliccando su un link non invia una nuova
richiesta al server. Intercettata la richiesta, l’applicazione riparte dal punto
in cui si era fermata, e continua l’esecuzione del programma, riga 6 del listato, catturando il parametro passato dalla pagina. Al passo seguente viene inviata una seconda pagina al client e rimane in attesa.
E’ evidente che con questo approccio possiamo scrivere il flusso di controllo
di un’applicazione web come quello di un normale programma.

La continuation
può semplificare molto lo sviluppo di applicazioni web. Lo schema
senza stati può esser ridisegnato
come in figura seguente:

Modello ?Applicazione?

Quando un utente avvia una applicazione è il webserver che ha il controllo.
Una applicazione web, invece di essere un insieme di richieste in
ordine casuale, diviene una conversazione con l’utente unificata e controllata.
I vantaggi offerti da questo approccio possono esser così schematizzati:

  • assenza di stato tra le richieste. Il framework può identicare una singola continuation dalla URL e salvarla in sessione. Di conseguenza l’architettura di comunicazione non viene appesantita, anzi nella pratica Cocoon si limita a passare un solo ID alfanumerico che serve ad identificare la continuation.
  • il modello di programmazione è di tipo stateful, ossia con informazioni
    sullo stato. Il framework è in grado di riportarsi in ogni momento ad
    ogni stadio del suo ciclo di vita.
  • è possibile invalidare la continuation, questo può essere utile, per
    esempio, nei casi di invii multipli dello stesso form.
  • il pulsante indietro del browser non crea problemi visto che il
    framework, si occupa di ripristinare anche gli stati precedenti.

Uno svantaggio è che il framework utilizza molta memoria per gestire
la continuation. Questo è da tenere in considerazione per fare un uso oculato
delle risorse. E’ comunque da segnalare che ormai la continuation
in cocoon è una implementazione matura; a conferma di questo ci sono le
esperienze pratiche da me maturate negli ultimi due anni, non si sono mai
verificati decadimenti delle prestazioni e/o condizioni di out of memory anche
in condizioni di stress per il framework.