Provavelmente isso não é novidade para alguns de vocês, mas já faz algum tempo que eu venho me aventurando pelo incrível mundo do Smalltalk. Dando prosseguimento à esta saga, tenho o orgulho de anunciar a minha próxima vítima: o framework Seaside!
O Seaside é um impressionante framework web escrito em Smalltalk focado no desenvolvimento de sites aplicações stateful. Mesmo conhecendo muito pouco a respeito do seu funcionamento — até então, nada além de um mísero hello world — resolvi tentar criar uma aplicação que realmente fizesse algo a mais do que simplesmente escrever um texto na tela.
A experiência foi muito boa, mas poderia ter sido melhor se o projeto contasse com uma documentação decente. O código do framework propriamente dito é muito bem documentado e é possível aprender bastante, mesmo tendo apenas o Class Browser do Squeak como recurso de pesquisa. Mas convenhamos: uma documentação como esta ainda faz falta, principalmente para os aventureiros que, como eu, não fazem parte do core team do Seaside.
Entendendo o exemplo
Não mostrarei nada de muito complexo aqui, até porque eu também estou aprendendo. Mas o exemplo é interessante para conhecermos algumas das características principais do Seaside, que serão explicadas no decorrer deste post.
Você conhece aquele jogo de “adivinhar o número”? Pois então… criaremos aqui algo parecido. Ao lado, você pode conferir uma imagem que ilustra como a aplicação deverá funcionar.Ao abrir a aplicação, o usuário irá ver uma mensagem, que deverá confirmar para que o jogo comece. Quando o usuário confirma, o sistema gera um número aleatório entre 1 e 10 e mostra um formulário, que o usuário deve preencher com seu palpite. Em seguida, o sistema checa se o palpite do usuário corresponde ao número gerado aleatoriamente: se sim, o sistema mostra uma mensagem parabenizando o usuário; caso contrário, o sistema re-exibe o formulário, mostrando a contagem de “chutes” até o momento, além de uma mensagem com uma dica, para ajudar o usuário a adivinhar o número.
Esse ciclo continua até o usuário acertar. Por fim, o sistema pergunta se o usuário deseja jogar novamente: em caso afirmativo, um novo número é sorteado e o formulário re-exibido; senão, a mensagem de boas vindas é mostrada.
Ainda em relação ao diagrama, perceba que há um quadrado vermelho em torno de algumas atividades. Basicamente, isso quer dizer que a aplicação não deve permitir que o usuário volte (com o botão back do browser) e re-submeta um palpite após já tê-lo acertado. Em outras palavras, eu quero que as atividades dentro do quadrado vermelho representem um workflow, ou transação.
Introdução
O Seaside trabalha com o conceito de componentes. Neste exemplo, criaremos dois componentes para implementar a aplicação proposta. Um será responsável por controlar o fluxo de navegação do usuário pela aplicação, e o outro que receberá o input do usuário através de um formulário. Mas, antes de prosseguirmos, vamos aprender para que servem duas das mais importantes classes do Seaside: WAComponent e WATask.
A classe WAComponent será sua melhor amiga em um projeto Seaside, pois é através da criação de sub-classes de WAComponent que construímos a aplicação, pedaço por pedaço. Toda sub-classe de WAComponent deve implementar o método #renderContentOn:, pois é através deste que definimos como o componente deve ser renderizado no browser do usuário.
A classe WATask, por sua vez, é parecida com a classe WAComponent (de fato, ela a estende), com a diferença que ela não é usada para nada além de definir uma tarefa, ou melhor, a ordem com que outras tarefas e componentes devem ser chamados de modo a cumprir um determinado objetivo. Em poucas palavras: estenda WATask para definir workflows!
Entendendo a interação entre os componentes
Como pôde ser visto no diagrama de atividades mostrado anteriormente, o fluxo começa quando o usuário confirma que deseja jogar, passando o controle para o fomulário. O formulário é então renderizado no browser do usuário para que ele possa enviar um palpite. Em seguida, quando o usuário submete o formulário, o componente chamador (que é o próprio fluxo) recebe o palpite do usuário e o compara com o número gerado no início. Dependendo do resultado dessa comparação, o formulário é exibido novamente ou a mensagem de sucesso é mostrada.
Certo… talvez tenha ficado confusa a parte em que um componente “passa o controle” para outro. Esse é um conceito muito importante em um framework como o Seaside. Para entender essa coisa do “passa o controle”, imagine uma chamada de método comum:
void main(char** args) { foo(); // chama o método ("passa o controle") } void foo() { // faz algo aqui }
Neste exemplo de código C, quando chamamos um método, o fluxo de execução é modificado, fazendo com que o método chamado assuma o controle. Então, quando o método termina, o fluxo retorna ao método chamador.
Simples não? Agora imagine isso funcionando na web. Imagine que o método main do exemplo fosse um componente da nossa aplicação, e que esse componente pudesse passar o controle para outros componentes, assim como a chamada de método no exemplo, só que com a possibilidade de manter essa pilha de execução entre as requisições HTTP! Isso resolveria um monte de problemas, não é?
Felizmente, é assim que o Seaside trabalha. O conceito por trás disso é chamado de Continuation. A imagem a seguir tenta ilustrar o funcionamento disso no contexto do nosso exemplo:
Se desconsiderarmos por um momento o modelo (limitado) de request/response no qual o protocolo HTTP se baseia, a sequência de interações do usuário para com a aplicação se parece com o especificado no diagrama acima. No diagrama, é como se tivéssemos uma pilha de chamadas que não se perde entre as requisições!
O foco deste post não é explicar a teoria por trás desses conceitos todos, portanto, se você tem interesse em saber mais sobre o assunto, use o Google!
Definindo o fluxo da aplicação
Vamos criar o componente responsável por controlar o fluxo de navegação da aplicação. O componente a seguir estende WATask e possui as variávies de instância number e form que servem, respectivamente, para armazenar o número gerado e o formulário usado para receber o input do usuário:
WATask subclass: #GuessingAppComponent instanceVariableNames: 'number form' classVariableNames: '' poolDictionaries: '' category: 'GuessingApp' GuessingAppComponent class>>canBeRoot ^true "Accessors for instance variables" GuessingAppComponent>>children ^ Array with: self form GuessingAppComponent>>initialize "Configure initial values" self number: 10 atRandom; form: (GuessNumberForm new) GuessingAppComponent>>go "Start application workflow" self inform: 'Start the game pushing the button below!!'. [self initialize; [self call: self form. self form number < self number ifTrue: [self form message: 'Try a higher value'] ifFalse: [self form message: 'Try a lower value']] doWhileFalse: [self form number = self number]] doWhileTrue: [self confirm: 'You won the game using ' , self form guesses asString , ' guess(es)!! Play again?']
Todo o código é bem simples de se entender, com exceção do método #go. Veja que o corpo do método não passa de uma transcrição dos diagramas mostrados anteriormente. Atenção especial para a chamada self call:, que é o equivalente ao exemplo de chamada de método, com a diferença que estamos transferindo o controle para que outro componente possa executar no lugar do componente corrente. Perceba também que este método ainda não está pronto, pois, do jeito que está, a aplicação não está preparada para caso o usuário queira re-submeter um palpite após já tê-lo acertado… faremos isso depois!
Recebendo o input do usuário
Abaixo segue o código do componente usado para receber o palpite do usuário. O componente estende WAComonent e tem as variáveis de instância number, message e guesses. Essas variáveis servem, respectivamente, para: guardar o palpite do usuário quando o formulário é submetido; guardar a dica a ser exibida para o usuário (algo do tipo “chute um valor mais alto”, ou “mais baixo”); e guardar o número de vezes que o formulário foi submetido:
WAComponent subclass: #GuessNumberForm instanceVariableNames: 'message number guesses' classVariableNames: '' poolDictionaries: '' category: 'GuessingApp' "Accessors for instance variables" GuessNumberForm>>increment "Increments the number of guesses" self guesses: self guesses + 1 GuessNumberForm>>initialize "Configure initial values" self number: 1; message: 'Try a number between 1 and 10'; guesses: 0. GuessNumberForm>>renderContentOn: html "Render component" html heading: self message level: 4. html heading: 'You did ' , self guesses asString , ' guess(es) until now' level: 5. html form with: [html textInput value: self number; callback: [:value | self increment; number: value asInteger; answer]. html submitButton value: 'check my guess!!']
Novamente, a simplicidade impera.
A única coisa deste código que vale explicar é o método #renderContentOn:, que serve para que possamos definir o que deve ser retornado ao usuário quando o componente precisar ser renderizado. O método recebe um objeto WAHtmlCanvas por parâmetro, responsável por gerar código XHTML automaticamente. Para quem tem conhecimentos em HTML, o código é bem trivial, com exceção do método #callback, onde colocamos algum código para ser executado quando o formulário for submetido. Perceba a chamada a self answer no final da linha, que é responsável por devolver o controle ao componente chamador (o que executou self call: form).
Demarcando uma transação
Se lembra do quadrado vermelho no diagrama de atividades? Então… precisamos dar um jeito de impedir que o usuário volte a dar palpites após ter descoberto o número. Ou seja: precisamos definir uma transação.
Isso é feito utilizando o método #isolate:, conforme pode ser visto abaixo:
GuessingAppComponent>>go "Start application workflow" self inform: 'Start the game pushing the button below!!'. [self initialize; isolate: [[self call: self form. self form number < self number ifTrue: [self form message: 'Try a higher value'] ifFalse: [self form message: 'Try a lower value']] doWhileFalse: [self form number = self number]]] doWhileTrue: [self confirm: 'You won the game using ' , self form guesses asString , ' guess(es)!! Play again?']
Não tem mais código, não! É só envolver o trecho desejado em um bloco e passá-lo ao método #isolate:!
O resultado
Confira abaixo algumas screenshots da aplicação em funcionamento:
O que há de errado com a última screenshot? Nada… é isso que acontece quando o usuário, após já ter adivinhado o número secreto, volta algumas telas com o botão back do browser e tenta re-submeter um formulário. Não cheguei a pesquisar, mas acredito que esta página seja customizável.
Desafio
Mesmo em uma aplicação simples como a que foi descrita aqui, fica evidente o quanto o Seaside é produtivo e simples (embora eu ainda não saiba usá-lo direito).
Portanto, proponho aos leitores deste blog que pensem no que deve ser feito para se criar uma aplicação igual (com as mesmas funcionalidades) a mostrada aqui (com a sua linguagem e framework preferidos). Tirando um ou outro, imagino que dê para contar nos dedos de uma só mão os frameworks capazes de fazer tanto com tão pouco. Enfim, sintam-se a vontade para expressar suas opiniões a respeito disso.
Conclusão
- Não precisei apelar para frameworks externos só para definir o workflow da aplicação;
- Não precisei fazer gambiarras para controlar o estado da aplicação (lembrando que a aplicação suporta diversas instâncias do browser acessando a mesma aplicação concorrentemente);
- Não precisei fazer gambiarras para tratar o botão back do browser;
- Não precisei fazer gambiarras para que o código XHTML gerado fosse válido;
- Não precisei ficar reiniciando o servidor web ou fazendo re-deploy o tempo todo;
- Não precisei pagar para conseguir um bom ambiente de desenvolvimento, pois o Squeak sozinho faz muito bem o serviço;
É… acho que esses motivos já são o suficiente para que você dê uma olhada no Seaside. Se não for, santo Deus, o que mais você quer?! ![]()
Veja também:
- JavaEE 5 interceptors
- Integração Contínua de aplicações Python com Hudson
- 6 lições que aprendi sobre desenvolvimento de softwares
- Test Driven Development com Java Swing
- VPN PPTP no Ubuntu Intrepid
Tags: aplicação, componentes, continuations, framework, jogo, seaside, smalltalk, squeak, tutorial, web, workflow





