Seaside: um framework web de verdade
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?!








“Mas convenhamos: uma documentação como esta (http://djangobook.com/) ainda faz falta, principalmente para os aventureiros que, como eu, não fazem parte do core team do Seaside.”
Agora temos http://book.seaside.st/book (embora ainda em evolução) que pode ajudar bastante no quesito documentação do Seaside.
Caramba! Muito, mas muito legal mesmo! Esse livro tem tudo para ser uma referência bem completa sobre o framework. Já coloquei nos favoritos.
Obrigado pelo link, Francisco!