Seaside: um framework web de verdade

004-0001.jpgNão é nenhuma novidade - pelo menos, não para aqueles que acompanham este blog há algum tempo - que eu estou me aventurando pelo incrível mundo do Smalltalk e suas vertentes. Pois então… dando prosseguimento à esta saga, tenho o orgulho de anunciar a minha próxima vítima: o framework Seaside.

Grossamente falando, o Seaside é um framework web escrito em Smalltalk que, provavelmente, será - a médio/longo prazo - tão (ou até mais) copiado do que o Rails é hoje. Pois bem… 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 sozinho… sem ajuda da escassa documentação. Qual foi o resultado?

O resultado não poderia ter sido melhor. O código do framework é muito bem documentado e é possível aprender bastante, mesmo tendo apenas o Class Browser do Squeak como recurso de pesquisa.

Enfim, não vou matar o assunto aqui pois temos um longo texto pela frente… mas vou logo avisando: ler a este post só é recomendável àqueles que possuem estômago forte. :P Só prossiga se quiser mesmo conhecer um framework web de verdade.

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.

state.pngVocê 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.

Enfim, 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.

Caso você opte por baixar o código, eu disponibilizei para download um arquivo zip contendo o projeto Squeak. Instruções sobre como rodar a aplicação se encontram em um arquivo read-me.

Introdução

O Seaside trabalha com o conceito de componentes. Nste exemplo, criaremos dois componentes para implementar a aplicação proposta. Um será responsável por controlar o fluxo da 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. 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:

  1.  
  2. public static void main(String[] args) {
  3.     foo(); // chama o método ("passa o controle")
  4. }
  5.  
  6. private static void foo() {
  7.     // faz algo aqui
  8. }
  9.  

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 outro componente (assim como a chamada de método no exemplo). Então, quando o componente chamado terminar seu trabalho, o controle volta ao componente chamador, exatamente como uma chamada a um método. Massa né?

Esse conceito é chamado de Continuation. A imagem a seguir tenta ilustrar o funcionamento disso no contexto do nosso exemplo:

sequence.png

Escrevendo o código!

Se você não está familiarizado com Continuations e não entendeu muito bem a explicação (confesso que não sei explicar direito isso)… sem problemas. Além do mais, tudo isso ficará mais claro ao olharmos os códigos.

Definindo o fluxo da aplicação

Vamos criar o componente responsável por controlar o fluxo de execuçã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 armazenar o formulário usado para receber o input do usuário:

  1.  
  2. WATask subclass: #GuessingAppComponent
  3.     instanceVariableNames: ‘number form’
  4.     classVariableNames:
  5.     poolDictionaries:
  6.     category: ‘GuessingApp’
  7.  
  8. GuessingAppComponent class>>canBeRoot
  9.     ^true
  10.  
  11. "Accessors for instance variables"
  12.  
  13. GuessingAppComponent>>children
  14.     ^ Array with: self form
  15.  
  16. GuessingAppComponent>>initialize
  17.     "Configure initial values"
  18.    
  19.     self number: 10 atRandom;
  20.         form: (GuessNumberForm new)
  21.  
  22. GuessingAppComponent>>go
  23.     "Start application workflow"
  24.    
  25.     self inform: ‘Start the game pushing the button below!!’.
  26.     [self initialize;
  27.         [self call: self form.
  28.             self form number < self number
  29.                 ifTrue: [self form message: ‘Try a higher value’]
  30.                 ifFalse: [self form message: ‘Try a lower value’]]
  31.                 doWhileFalse: [self form number = self number]]
  32.         doWhileTrue: [self confirm: ‘You won the game using ‘ , self form guesses asString , ‘ guess(es)!! Play again?’]
  33.  

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:

  1.  
  2. WAComponent subclass: #GuessNumberForm
  3.     instanceVariableNames: ‘message number guesses’
  4.     classVariableNames:
  5.     poolDictionaries:
  6.     category: ‘GuessingApp’
  7.  
  8. "Accessors for instance variables"
  9.  
  10. GuessNumberForm>>increment
  11.     "Increments the number of guesses"
  12.  
  13.     self guesses: self guesses + 1
  14.  
  15. GuessNumberForm>>initialize
  16.     "Configure initial values"
  17.  
  18.     self number: 1;
  19.         message: ‘Try a number between 1 and 10′;
  20.         guesses: 0.
  21.  
  22. GuessNumberForm>>renderContentOn: html
  23.     "Render component"
  24.  
  25.     html heading: self message level: 4.
  26.     html heading: ‘You did ‘ , self guesses asString , ‘ guess(es) until now’ level: 5.
  27.     html form
  28.         with: [html textInput value: self number;
  29.                 callback: [:value | self increment; number: value asInteger; answer].
  30.             html submitButton value: ‘check my guess!!’]
  31.  

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:

  1.  
  2. GuessingAppComponent>>go
  3.     "Start application workflow"
  4.    
  5.     self inform: ‘Start the game pushing the button below!!’.
  6.     [self initialize;
  7.         isolate: [[self call: self form.
  8.             self form number < self number
  9.                 ifTrue: [self form message: ‘Try a higher value’]
  10.                 ifFalse: [self form message: ‘Try a lower value’]]
  11.                 doWhileFalse: [self form number = self number]]]
  12.         doWhileTrue: [self confirm: ‘You won the game using ‘ , self form guesses asString , ‘ guess(es)!! Play again?’]
  13.  

Não tem mais código, não! É só envolver o trecho desejado em um bloco e passá-lo ao método #isolate:! :D

O resultado

Confira abaixo algumas screenshots da aplicação em funcionamento:

02.png 03.png
04.png 05.png

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). :P

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 os frameworks Wordpress-like, 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 fazer gambiarras (ou 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 (ou abas) acessando a mesma aplicação ao mesmo tempo);
  • Não precisei fazer gambiarras para evitar que o usuário faça alguma coisa errada (no caso, voltar com o botão back do browser ao terminar o jogo);
  • Não precisei fazer gambiarras para que o código XHTML gerado fosse válido;
  • Não precisei ficar gambiarras durante o desenvolvimento da aplicação (como reiniciar o servidor ou fazer re-deploy);
  • Não precisei fazer gambiarras (nem pagar) para conseguir um bom ambiente de desenvolvimento, pois o Squeak sozinho faz muito bem o serviço;
  • Não precisei fazer gambiarras, pois o Seaside não é um pseudo-component-based-framework (que mais complica do que ajuda) ou um brainless-action-based-framework (onde as actions nada mais são que métodos em forma de classes, procedurais e não reusáveis);
  • (esqueci de alguma coisa?)

Trocando em miúdos: Seaside ROCKS.

Imagem por: Google Images

Tags: , , , , , , , , ,