Montando um guestbook com Seaside + Magma
A pedidos (na verdade, era para estar no singular),
trago neste post informações sobre o Seaside e o Magma, mais especificamente sobre a integração entre eles com o objetivo de se criar aplicações que necessitam persistir objetos.
O Seaside dispensa apresentações. Já o Magma é um banco de dados multi-usuário orientado a objetos escrito totalmente em Smalltalk. Ele fornece alguns recursos como controle de concorrência via optimistic locking, protocolo simples para controle transacional, querying de objetos e tolerância a falhas.
Bom, pra falar a verdade, eu nem sei se tudo isso é verdade, mas é o que consta na página oficial do projeto. Se você tiver mais experiência sobre o Magma, sinta-se a vontade para comentar e/ou corrigir quaisquer erros que eu possa ter cometido.
Enfim, para que possamos aprender como integrar o Seaside e o Magma, criaremos uma aplicação simples de livro de visitas (guestbook), cujas mensagens enviadas pelos usuários serão persistidas em um repositório de objetos controlado pelo Magma.
Preparando a imagem
Supondo que você utiliza o Squeak e possui o Seaside devidamente instalado e configurado, o procedimento a seguir não é tão complicado. Primeiramente, abra o SqueakMap, clicando em World - Open - SqueakMap Package Loader. Em seguida, atualize a lista de pacotes selecionando a opção upgrade map from net a partir do menu de contexto. Finalmente, baixe os pacotes Magma: Magma client, Magma server e Magma tester. A versão que usei aqui foi a 1.0r39.
Para integrar o Seaside com o Magma, precisamos também instalar um pacote chamado Magma seaside (que, por sinal, não está disponível para download através do SqueakMap). Para baixar, abra o Monticello, clicando em World - Open - Monticello Browser. Clique no botão + Repository e adicione um repositório HTTP:
-
-
MCHttpRepository
-
location: ‘http://www.squeaksource.com/Magma’
-
user: ”
-
password: ”
-
Ok… selecione o repositório recém-criado e pressione o botão Open para que seja exibida a lista de pacotes do repositório. Na lista que aparecer, selecione o pacote Magma seaside, selecionando também a última versão disponível (aqui eu usei a versão 4) na lista da direita. Pressione o botão Merge para baixar e incluir o pacote na sua imagem. Por fim, salve a imagem para manter as modificações.
Criando o repositório Magma
Agora que temos a imagem pronta, podemos aproveitar para criar o repositório (ou banco de dados), que será usado pela nossa aplicação para armazenar e recuperar objetos.
Abra uma instância do Workspace e execute o código abaixo:
-
-
MagmaRepositoryController create: ‘guestbook’ root: Dictionary new.
-
Aguarde alguns segundos para que o Magma crie o repositório. Este comando que acabamos de executar cria uma pasta guestbook no diretório onde sua imagem está situada, com alguns arquivos dentro (applied.images, commitPackages, objects e objects.idx). Como eu não sei para que eles servem, então não vou tentar explicar aqui!
O que deve ser compreendido é que um banco orientado a objetos é diferente de um banco relacional. Não há tabelas, onde seus clientes, pedidos e chamados são agrupados. O que existe são apenas… objetos!
Neste exemplo, definimos um objeto-raíz do tipo Dictionary. Este objeto serve para que possamos, através dele, chegar aos outros objetos (nossos clientes, pedidos e chamados, por exemplo). Qualquer objeto pode ser utilizado como objeto-raíz: você pode criar uma classe MyRepository (contendo métodos e atributos que refletem suas estruturas de dados) e setar um objeto desta classe como objeto-raíz. No entanto, a utilização de um objeto Dictionary (que basicamente é uma coleção de pares do tipo chave-valor) é recomendada por facilitar a organização dos objetos em sub-grupos.
Criando uma aplicação Seaside integrada ao repositório Magma
Inicie o servidor HTTP:
-
-
WAKom startOn: 9090.
-
Abra o utilitário de configuração e informe o usuário e senha, se necessário. Adicione um novo Entry point chamado guestbook. Configure da seguinte forma:
O pacote Magma seaside fornece algumas classes para facilitar a integração de uma aplicação Seaside com um repositório Magma. O que precisamos fazer foi criar a aplicação, tendo como classe-base de configuração a classe WAMagmaConfiguration, fornecida pelo referido pacote. Ao utilizar esta classe, a classe WASession é automaticamente substituída por WAMagmaSession, que é uma especialização de WASession capaz de controlar a sessão Magma.
Apenas para constar, a integração do Seaside do Magma pode ocorrer de duas formas: através de um repositório local ou remoto. A opção entre uma forma ou outra depende basicamente da carga de usuários que a aplicação deverá suportar. Mais detalhes sobre isso podem ser conferidos na página do projeto Magma.
Salve as configurações.
O que será criado?
Veja algumas screenshots da aplicação rodando… é assim que ela ficará após concluída:
Desenvolvendo a aplicação
A nossa mensagem
Primeiramente, veja a classe GuestbookMessage (que serão as mensagens do nosso livro de visitas). Trata-se de um POSO (existe este termo?!)
:
-
-
Object subclass: #GuestbookMessage
-
instanceVariableNames: ‘date fromName email url message’
-
classVariableNames: ”
-
poolDictionaries: ”
-
category: ‘Guestbook-App’
-
-
"Accessor methods and initializer"
-
Definindo o componente-raíz
Precisamos criar o componente-raíz da aplicação web. Veja:
-
-
WAComponent subclass: #GuestbookView
-
instanceVariableNames: ‘listComponent’
-
classVariableNames: ”
-
poolDictionaries: ”
-
category: ‘Guestbook-App’
-
-
"Accessor methods"
-
-
GuestbookView>>children
-
^Array with: self listComponent
-
-
GuestbookView>>renderContentOn: html
-
self listComponent messages: self loadMessages.
-
html heading: ‘Guestbook application’.
-
html paragraph
-
with: [html render: self listComponent].
-
html paragraph
-
with: [html text: ‘* batteries not included, 2007. All rights NOT reserved! :-)’]
-
-
GuestbookView>>loadMessages
-
| data |
-
self session
-
commit: [data := self session root at: ‘messages’ ifAbsentPut: OrderedCollection new].
-
^ data
-
Dando uma olhada no método #renderContentOn:, podemos considerar este componente como sendo um template, pois ele servirá para renderizar um cabeçalho e um rodapé em todas as telas da aplicação. Isso é possível através do uso dos métodos #call: e #answer: ao longo da implementação, fazendo com que a “casca” da página seja renderizada sempre, enquanto o corpo da página a ser renderizada dependa do componente no controle. Mais detalhes sobre esse comportamento podem ser vistos em um post anterior aqui do blog.
Voltando ao assunto, veja que esta classe possui uma variável de instância listComponent, que será utilizada para guardar uma referência ao componente responsável por exibir a lista de mensagens cadastradas. O método #renderContentOn: solicita a renderização deste componente através de uma chamada ao método #render:.
O método #loadMessages também é interessante, pois é através deste método que recuperamos as mensagens cadastradas do repositório.
Toda operação a ser feita em um repositório Magma deve ser feita dentro de uma transação:
-
-
| mySession |
-
mySession := "get Magma session object".
-
mySession commit: [ "put database operations here" ].
-
"modifications are already persistent at this point"
-
No caso da nossa aplicação Seaside, a sessão Magma pode ser obtida através de uma chamada ao método self session:
-
-
"somewhere inside a Seaside component…"
-
| mySession rootObj |
-
mySession := self session. "WAMagmaSession object"
-
rootObj := mySession root. "this is the root object"
-
Ainda sobre o método #loadMessages…. você se lembra de que foi dito que é recomendado utilizar um Dictionary como objeto-raíz do repositório para facilitar a organização do mesmo em sub-grupos? Então… neste nosso aplicativo, todas as mensagens cadastradas são agrupadas sob a chave 'messages', em um objeto OrderedCollection, que contém as mensagens na ordem em que elas são inseridas.
Criando um componente para listar as mensagens
Veja, abaixo, o código principal do componente MessageList, que servirá para renderizar uma tabela HTML com as mensagens cadastradas (além de um link que leva à página de cadastro de mensagens):
-
-
WAComponent subclass: #MessageList
-
instanceVariableNames: ‘messages’
-
classVariableNames: ”
-
poolDictionaries: ”
-
category: ‘Guestbook-App’
-
-
"Accessor methods and initializer"
-
-
MessageList>>removeSelected: aMessage
-
self
-
isolate: [(self confirm: ‘Do you really want to remove that message?’)
-
ifTrue: [self session
-
commit: [(self session root at: ‘messages’)
-
remove: aMessage
-
ifAbsent: []]]]
-
-
MessageList>>renderContentOn: html
-
html
-
paragraph: [self messages
-
ifEmpty: [html heading: ‘No messages yet.’ level: 4]
-
ifNotEmpty: [html table border: 1;
-
with: [html
-
tableRow: [html
-
tableHeading: [html text: ‘Date’].
-
html
-
tableHeading: [html text: ‘Name’].
-
html
-
tableHeading: [html text: ‘URL’].
-
html
-
tableHeading: [html text: ‘Message’].
-
html
-
tableHeading: []].
-
self messages
-
reverseDo: [:each | html
-
tableRow: [html
-
tableData: [html text: each date].
-
html
-
tableData: [html anchor url: ‘mailto:’ , each email;
-
with: each fromName].
-
html
-
tableData: [html anchor url: each url;
-
with: each url].
-
html
-
tableData: [html text: each message].
-
html
-
tableData: [html anchor
-
callback: [self removeSelected: each];
-
with: ‘X’]]]]]].
-
html
-
paragraph: [html anchor
-
callback: [self
-
isolate: [self call: MessageForm new]];
-
with: ‘Add a new message’]
-
Este componente é exibido assim que o usuário acessa a aplicação, iterando em ordem reversa pela lista de mensagens cadastradas, mostrando-as em uma tabela. Se ele deseja então cadastrar uma nova mensagem, ele clica no link (que faz com o componente MessageForm assuma o controle, sendo renderizado no lugar do componente que o chamou). Enfim, nada de novo aqui.
Destaque para o método #removeSelected:, que obtém a mensagem correspondente e a remove do repositório (após pedir uma confirmação).
Criando um componente para incluir uma mensagem
Abaixo, segue o código do componente MessageForm, utilizado para receber os dados do usuário através de um formulário e cadastrar a mensagem no repositório. Não há validação, nem nada disso… só estamos interessados em ver como o Magma se integra ao Seaside, tá lembrado?
-
-
WAComponent subclass: #MessageForm
-
instanceVariableNames: ‘message’
-
classVariableNames: ”
-
poolDictionaries: ”
-
category: ‘Guestbook-App’
-
-
"Accessor methods and initializer"
-
-
MessageForm>>insertMessage
-
self session
-
commit: [(self session root at: ‘messages’)
-
add: self message].
-
self answer
-
-
MessageForm>>renderContentOn: html
-
html
-
paragraph: [html
-
form: [html
-
table: [html
-
tableRow: [html
-
tableData: [html text: ‘Name:’].
-
html
-
tableData: [html textInput on: #fromName of: self message]].
-
html
-
tableRow: [html
-
tableData: [html text: ‘Email:’].
-
html
-
tableData: [html textInput on: #email of: self message]].
-
html
-
tableRow: [html
-
tableData: [html text: ‘URL:’].
-
html
-
tableData: [html textInput on: #url of: self message]].
-
html
-
tableRow: [html
-
tableData: [html text: ‘Message:’].
-
html
-
tableData: [html textArea rows: 6;
-
columns: 50;
-
on: #message of: self message]].
-
html
-
tableRow: [html tableData colSpan: 2;
-
style: ‘text-align: right’;
-
-
with: [html submitButton
-
callback: [self insertMessage];
-
value: ‘Send!’]]]]].
-
html
-
paragraph: [html anchor
-
callback: [self answer];
-
with: ‘Go back’]
-
Nada de novo. Se o usuário decide voltar, ele pressiona o link Go back, fazendo com que o componente devolva o controle ao chamador (que, no caso, é o componente MessageList). Se o usuário preenche o formulário e o submete, o objeto GuestbookMessage é preenchido com os dados do formulário e o método #insertMessage é invocado.
O método #insertMessage, por sua vez, serve para pegar a mensagem digitada pelo usuário e adicionar ao repositório, devolvendo o controle ao componente MessageList em seguida.
Conclusão
É isso mesmo… a aplicação está pronta! Em poucos minutos, criamos uma aplicação web de livro de visitas com o Seaside, com suporte à persistência de objetos via repositório Magma. Uma vez configurada a aplicação, a integração entre os frameworks ocorre de forma praticamente transparente.
Só para dar uma instigada… esta aplicação poderia ser ter sido codificada ainda mais rapidamente, usando o Magritte. Com o Magritte, nós criaríamos métodos a nível de classe que especificariam meta-dados para a classe GuestbookMessage (um workaround para se conseguir algo como as annotations do Java, eu suponho), sendo possível gerar o código de formulários HTML dinamicamente (inclusive com suporte a validação) com apenas um comando!
Ficou curioso? Aguarde os próximos posts!
Foto por: OllieD
Tags: componentes, framework, integração, magma, magritte, monticello, oodb, seaside, smalltalk, squeak, tutorial, web

1 de junho de 2007 às 9:07 am
Cara, você é rápido!!!
ficou bem legal seu tutorial (apenas li, não ‘testei’ ainda
).
Quanto ao magritte, ainda não tive tempo para testá-lo, aliás nem sei direito seu papel. (sem pressão ok
)
Mais uma vez parabéns, e seu blog entrou definitivamente no meu ‘Favoritos | Smalltalk’
[]s
1 de junho de 2007 às 11:33 am
Heheheh… viu só a velocidade?!
Só foi “fácil” assim porque eu encontrei as informações de que precisava na Internet… só apanhei um pouco ao utilizar o Monticello (nunca tinha mexido nele antes) para instalar o pacote Magma seaside.
Valeu por adicionar este blog aos seus favoritos! Se tiver outras sugestões, é só mandar!
[]s!
10 de setembro de 2007 às 10:02 pm
Hi
I like your logo. It very impressive, many thanks. Good resources here. Thanks!
G’night