Continuations, Conversations, Session, Sticky Actions?

14 respostas
saoj

No código abaixo o framework Riffe utiliza continuations para armazenar valores temporários na sessão, sem tem que usar a sessão. Basicamente o pause() espera de alguma maneira o cliente entrar com o valor no formulário para continuar.

No final ele limpa o contexto do continuation.

Qualquer seria a real vantagem disso ao invés de ussar a session, guardar as coisas na session e no final limpar a session???

Comparando com a versão em Mentawai, percebo que o código com continuation é um pouco mais limpo, pois não precisa ficar colocando e tirando as coisas da session. Tirando isso não vejo mais nenhuma vantagem.

Opiniões / Comentários ???

RIFFE:

public class Game extends Element {
	private static Random randomNumbers = new Random();
	public void processElement() {
		
		Template template = getHtmlTemplate("game");
		int answer = 0, guesses = 0, guess = -1;
		
		answer = randomNumbers.nextInt(101);
		while (guess != answer) {
			print(template);
			
			pause();
			
			template.clear();
			
			guess = getParameterInt("guess", -1);
			if (guess &lt 0 || guess &gt 100) {
				template.setBlock("warning", "invalid");
				continue;
			}
			guesses++;
			
			if (answer &lt guess)      template.setBlock("msg", "lower");
			else if (answer &gt guess) template.setBlock("msg", "higher");
		}
		
		ContinuationContext.getActiveContext().removeContextTree();
		
		template = getHtmlTemplate("success");
		template.setValue("answer", answer);
		template.setValue("guesses", guesses);
		print(template);
	}
}

Mentawai:

package examples.numberguess;

import java.util.*;

import org.mentawai.core.*;
import org.mentawai.validation.*;
import org.mentawai.rule.*;

public class NumberGuess extends BaseAction implements Validatable {
    
    public static final String BINGO = "bingo";
    public static final String FIRST = "first";
    public static final String WRONG = "wrong";    
    
    private static final Random random = new Random();
    
    private void incrementGuesses() {
        
        Integer i = (Integer) session.getAttribute("guesses");
        
        session.setAttribute("guesses", new Integer(i.intValue() + 1));
        
    }
    
    public void initValidator(Validator val, String innerAction) {
            
            val.add("guess", new IntegerRule(0, 100), "Invalid guess!");
            
    }
    
    public String execute() throws Exception {
        
        // Check if a game is in progess, by fetching the current answer...
        
        int answer = -1;
        
        if (session.getAttribute("answer") == null) {
            
            answer = random.nextInt(101);
            
            session.setAttribute("answer", new Integer(answer));
            
            session.setAttribute("guesses", new Integer(0));
            
        } else {
            
            answer = ( (Integer) session.getAttribute("answer")).intValue();
            
        }
        
        if (input.getValue("guess") != null) {
            
            incrementGuesses();
            
            int guess = input.getIntValue("guess");
            
            if (answer == guess) {
                
                output.setValue("answer", session.getAttribute("answer").toString());
                
                output.setValue("guesses", session.getAttribute("guesses").toString());
                
                session.removeAttribute("answer");
                
                session.removeAttribute("guesses");
                
                return BINGO;
                
            } else {
                
                if (guess &lt answer) {
                    
                    addMessage("Number is higher!"); // of course this can be i18n...
                    
                } else {
                    
                    addMessage("Number is lower!"); // of course this can be i18n...
                    
                }
                
                return WRONG;
            }
        }
        
        return FIRST;
    }
}

14 Respostas

saoj

E se ao invés de "pausarmos" como o Riffe faz ou usar aquela maluquice de conversation que o Seam faz, criássemos uma Sticky action, isto é, uma action que teria sua instância persistida no escopo da sessão do usuário de forma que ela pudesse ser reutilizada em requisições subsequentes mantendo assim seu estado ??? (stateful actions ???)

public interface Sticky {

    public void adhere();

    public void disjoin();

}

Daí se sua action implementa Sticky vc pode chamar adhere() que a instancia da action vai ficar guardadinha na session até que vc chama disjoin().

Resultado:

package examples.numberguess;

import java.util.*;

import org.mentawai.core.*;
import org.mentawai.validation.*;
import org.mentawai.rule.*;

public class NumberGuessSticky extends BaseAction implements Validatable {
    
    public static final String BINGO = "bingo";
    public static final String FIRST = "first";
    public static final String WRONG = "wrong";    
    
    private int answer = -1;
    
    private int guesses = 0;
    
    private static final Random random = new Random();
    
    public void initValidator(Validator val, String innerAction) {
            
            val.add("guess", new IntegerRule(0, 100), "Invalid guess!");
            
    }
    
    public String execute() throws Exception {
        
        System.out.println("Executing action: " + this.toString());
        
        // Check if a game is in progess, by fetching the current answer...
        
        if (answer == -1) {
            
            answer = random.nextInt(101);
            
            guesses = 0;
            
            adhere();
            
        }
        
        if (input.getValue("guess") != null) {
            
            guesses++;
            
            output.setValue("guesses", String.valueOf(guesses));
            
            int guess = input.getIntValue("guess");
            
            if (answer == guess) {
                
                output.setValue("answer", String.valueOf(answer));
                
                disjoin();
                
                return BINGO;
                
            } else {
                
                if (guess &lt answer) {
                    
                    addMessage("Number is higher!"); // of course this can be i18n...
                    
                } else {
                    
                    addMessage("Number is lower!"); // of course this can be i18n...
                    
                }
                
                return WRONG;
            }
        }
        
        return FIRST;
    }
}
ricardolecheta

saoj:
reutilizada em requisições subsequentes mantendo assim seu estado ??? (stateful actions

a 2ª opção deixaria seu server mais carregado é claro.

A diferença entre seu código e do RIFE é que do RIFE nao tem if.
Com webwork fica um pouco mais limpo o código:

public class Guess extends ActionSupport {
    int guess;

    public String execute() throws Exception {
        int answer = new Random().nextInt(100) + 1;
        int tries = 5;

        while (answer != guess && tries &gt 0) {
            pause(SUCCESS);

            if (guess &gt answer) {
                addFieldError("guess", "Too high!");
            } else if (guess &lt answer) {
                addFieldError("guess", "Too low!");
            }

            tries--;
        }

        if (answer == guess) {
            addActionMessage("You got it!");
        } else {
            addActionMessage("You ran out of tries, the answer was " + answer);
        }

        return SUCCESS;
    }

    public void setGuess(int guess) {
        this.guess = guess;
    }
}

Mas assim, este é um exemplo helloworld, teria que ver na prática com fluxos mais complexos qual a vantagem real, mas com certeza vc nao precisa manipular a session e fazer muitos ifs no código

saoj

Acho que vc não entendeu, Ricardo. O teu código está errado, pois vc não está persistindo a resposta final (answer).

Esse exemplo que vc colocou aí não guarda qualquer tipo de estado, ou seja, onde vc vai guardar a resposta do jogo, a quantidade de guesses, tempo corrido, etc ???

Resposta ultrapassada: session (veja o exemplo do Mentawai que usa a session para fazer isso)

Resposta moderna: sticky actions, ou seja, o estado a guardado na propria action que persiste a partir de um adhere até o cara fazer um disjoin().

Não ficou nem um pouco pesado, já que a instancia da action será guardada na propria session do cara. Dá uma olhada no código do menta que está no SVN que vc vai ver.

E o código do WW não fica mais limpo não. Esse teu código não está tratando um monte de detalhes que o código do menta está, como validação por exemplo. :wink:

J

Bem, você tem que tomar cuidado com alguns aspectos da sua “Sticky” action, coisas como cluster, timeout essas coisas… No final você estará implementando um EJB Statefull. :slight_smile:

O Seam por sua vez, em vez de reinventar a roda, utiliza de todo o mecanismo do EJB 3 (opcionalmente) para ter as suas “Sticky” actions.

Continuations, Conversations e Statefull são tudo coisas que já fazemos na mão no dia dia, como você mesmo constatou. O que os frameworks fazem é justamente fazer e controlar isso para agente. E com mecanimos plugáveis que dependendo da necessidade podemos criar regras sofisticadas ou não.

O uso do EJB 3 pelo Seam é legal por causa disso, se eu tenho uma aplicação de alta-disponibilidade tenho todo um cluster, load-balance, real-time, cache de um WebLogic… Se não fico com toda a simplicidade de um Tomcat. (que na maioria das vezes é mais do que necessário).

Agora, se você está pensando em simplemente serializar a action e colocar na HTTPSession você estará criando problemas de escalabilidade no seu framework.

J

Editei para colocar um exemplo com DB que fica mais dia-a-dia

Bem, pesse no seguinte código:

public executeComContinuation() { try { db.open(); db.insert(...); pause(); db.insert(...); pause(); db.insert(...); db.commit(); } catch (TimeOutException e) { db.rollback(); } finally { db.close(); } }

No código acima, se o usuário não finaliza a action (acessando a sua action mais 2 vezes) você terá a transação cancelada e o banco fechado.
Você não precisa controlar isso.

É mais do que colocar e tirar variaveis de sessão.

saoj

Obrigado pelo bom comentário, juzepeleteiro.

Quanto a cluster, realmente teremos que pensar melhor, pois a instancia da action não vai poder ser serializada e passada para o outro lado. Até daria para fazer um Stub ou objeto transportador para tal, mas aí teria que fazer algumas brincadeiras com serialização que fogem do escopo aqui.

Quando a timeout, isso sai na urina, pois é o próprio timeout da session!

EJB é claro que já tem isso, mas não estamos querendo usar EJB aqui.

Logo a conclusão que eu chego é:

JBoss Seam usa conversations

Rife usa continuations

Mentawai usa Sticky Actions

O objetivo aqui é fazer uma comparação desses três para se for o caso tentar melhorar esse esquema do Mentawai.

Realmente é 10 vezes melhor que trabalhar com sessão…

J

Lembre-se que a Action têm que ser informada do timeout, para poder finalizar e tomar algumas ações. Veja o exemplo que dei de continuation. (Editei para colocar um exemplo com DB que fica mais dia-a-dia).

saoj

Teria então que se implementar HttpBindingListener e no momento que a session esperar chamar uma função destroy da vida que poderia fazer um cleanup no estado da action, antes dela sumir.

Boa observação.

J

Apartei o post duas vezes sorry…

J

saoj:

No código acime, se o usuário não acessar a sua action mais 2 vezes você terá o writer fechado de qualquer maneira. Você não precisa controllar isso. Penso no mesmo código para banco de dados, você inicializa um transação que dura por algumas request e se o cara abandonar, der timeout, (o pause vai gerar uma exception) você da um rollback.

Teria então que se implementar HttpBindingListener e no momento que a session esperar chamar uma função destroy da vida que poderia fazer um cleanup no estado da action, antes dela sumir.

Boa observação.

Mais do que isso, try…catch…finally com o evento de TimeOut você resolve o catch, mas e o finally?

Por que você não integra o continuations do Rife, assim como fez o Webworks, no Menta? Fica elegante.

Luca

Olá

Só para lembrar: não confundir a classe Continuations do RIFE que é apenas uma ThreadLocal para guardar estado com o conceito de continuations usado com Ajax que tem uma boa explicação no site do jetty.

[]s
Luca

J

Para ser honesto eu nunca usei os Continuations do RIFE nem do Jetty, mas pelo o que eu li do RIFE ele faria (não sei como ele implementa) o código que escrevi acima.

(Meu background de Continuation vem de Ruby).

saoj

O continuation do RIFE é diferente das sticky actions do menta. O Rife literalmente pausa, daí ele pode jogar uma exception para cair num finally da vida. Já no menta não há pausa e sim persistência da instancia da action no escopo da sessão para manter estado.

Se isso é melhor ou pior eu ainda não sei, mas no caso de um crash ambas alternativas não serão processadas.

Essa questão de rollback me parece importante mas confusa. Num cadastro onde há dezenas de telas, não seria melhor ir armazenando em session (ou na sticky session) para só depois fazer um dump para o banco ? Tá certo que de novo caímos no problema de cluster de session, mas manter uma conexão aberta durante muito tempo entre requisições, sem dar commit para depois dar rollback me parece péssimo tb. E se for dando commit a cada tela depois vai ter que limpar o banco, o que é pior ainda.

Vejo que o webwork implementa continuations com manipulação de bytecode: http://www.opensymphony.com/webwork/wikidocs/Continuations.html

Eu achei essa solução de Sticky Actions bem interessante, clean e fácil. Fazer com que uma action seja re-executada do meio dela, como se fosse um thread bloqueado, me parece meio doido e contra-intuitivo.

Ainda estou assimilando essas idéias, logo qualquer comentário/ajuda será bem-vindo. Alguém consegue pensar em alguma situação que as Sticky Actions não vou resolver ??? Lembre-se que podemos implementar facilmente o timeout com a interface HttpSessionBindingListener, ou seja, quando a session do cara espirar ele terá uma oportunidade de fazer um cleanup no state da sticky action.

J

Sim, se você pensar em SQL diretamente com o o JDBC você está certo, e melhor ficar guardando na session mesmo… agora se você estiver falando de Hibernate por exemplo (lembrando que um transaction do Hibernate não é necessáriamente um do JDBC) o negócio funciona e é muito mais natural e rapido. Voce eliminina completamente DTO e etcs… Alem de ter a vantagem do cache L2 do Hibernate.

Criado 28 de agosto de 2006
Ultima resposta 29 de ago. de 2006
Respostas 14
Participantes 4