2008/01/29

Mais lamúrias sobre frameworks Web

Continuando a fuçar "onde foi que eu errei" a respeito do Luca e dos frameworks para Web, cheguei a algumas conclusões parciais.

Primeiro, durante o desenvolvimento do Luca apareceu um pattern interessante: a "representação de dados" em formatos diversos, e a conversão de uma representação para outra por meio de filtros. O Luca lida com dados em 3 formatos distintos:

Representação SQL (RS): É a que pode ser oferecida diretamente ao Model, no caso o SQLObject. Por conseqüência, é praticamente pareada com a estrutura da tabela SQL. Também é o formato obtido quando se puxa dados via SQLObject.

Representação intermediária (RI): O controlador só manipula e valida dados nesse formato, portanto pode ser considerado o "principal". As demais representações só existem para satisfazer outros componentes.

Representação Web (RW): Formato que visa satisfazer ao template XML. Também é o formato que é obtido quando o usuário submete dados via formulário Web.

O controlador deve implementar os filtros de conversão entre RS, RI e RW,, mas ele herda filtros default suficientemente poderosos para as conversões triviais, de strings e inteiros, que são a maioria. Apenas os dados "excepcionais", cuja representação no SQL não tenha nada a ver com o apresentado na página HTML, precisam ser convertidos pelo controlador.

Com isso eu tentei isolar as "regras de negócio" do controlador ao máximo, tornando-as independentes tanto dos detalhes desagradáveis do SQLObject quanto dos dados vindos diretamente da Web (que podem estar sujos ou num formato não ideal). Também tentei economizar código, de modo que especificando as colunas numa única tabela molda todo o comportamento do controlador, evitando ter de repetir as mesmas variáveis um monte de vezes.

Infelizmente, as colunas ainda precisam ser especificadas novamente nos templates XML e nos validadores Turbogears (que no Luca são utilizados para apenas converter tipos, não para a validação completa). Este fato é uma das minhas principais broncas.

Então, pensando em termos de processamento de dados, tudo que é mostrado na Web vem de um banco de dados. De forma simétrica, tudo que o usuário faz no sistema tem como único objetivo final gravar dados no banco. Entra porco, sai lingüiça. O fluxo de dados de SQL para HTML é:

SQL -> Model -> RS -> Filtro SI -> RI -> Filtro IW -> RW -> XML -> HTML

O fluxo de dados do formulário até o SQL é um pouco diferente, na ponta Web:

Form -> Validador TG -> RW -> Filtro IW -> RI -> Filtro SI -> RS -> Model -> SQL

O problema da promiscuidade entre Model, View e Controller é que quase todos os blocos acima estão contidos no Controller: RS, Filtro SI, Ri, Filtro IW, RW e validador TG. O Controller está trabalhando demais.

O Model deveria, além de esconder o banco de dados SQL, também implementar a representação intermediária (RI). Ou seja, ele deveria fornecer e aceitar dados no formato predileto do Controller, inclusive fazendo a validação. O Model ideal é muito mais que um simples mapeamento SQL-objeto. Isso encurta o fluxo para:

Model -> RI -> Filtro IW -> RW -> XML -> HTML

O próximo problema é a representação Web. Como os templates XML são linguagens canhestras, o Controller tem de trabalhar mais, oferecendo dados "pré-mastigados" ao template. Daí a necessidade do filtro IW.

A rigor esse filtro deveria estar numa classe View separada do Controller; o que não é possível no Turbogears porque o View é apenas o mecanismo de template XML, e escrever um View "completo" exige código de verdade. Nesse cenário ideal, o fluxo fica

Model -> RI -> View{Filtro IW, RW, XML, HTML}

Até aqui, "consertar" o Luca demandaria as seguintes providências:

* Escrever Models melhores, que isolem completamente o SQLObject do resto da aplicação e interfaceiem dados no formato predileto dos Controllers -- o RI.

* Escrever decorators View mais poderosos, que aceitem diretamente dados RI e virem-se para transformar dados "puros" numa página Web.

Mas a contaminação dos Controllers não se resume ao fluxo de dados -- ela também ocorre nas chamadas a métodos. Por que?

Porque é justamente esta a idéia do CherryPy, componente do Turbogears: se você invoca uma página bla.com.br/bla/ble, isso provoca a chamada a um método "ble" do objeto Controller "bla". Sem dúvida é uma coisa boa, mas talvez o objeto "bla" não devesse ser um Controller no sentido MVC do termo...

Se os métodos do Controller são moldados de acordo com as páginas Web, é óbvio que o controlador vai ficar viciado com o jeito Web de ser. Se for necessária qualquer outra interface que não Web, já vai ficar complicado ou impossível usá-lo para atender outros tipos de cliente, como texto, GUI... ou mesmo AJAX.

O cliente AJAX é um caso interessante porque do ponto de vista do controlador ele não é Web, pois o cliente AJAX troca dados puros com o controlador (codificados em JSON), e não páginas ou formulários. Um controlador "viciado" para Web não servirá nem para AJAX. Será preciso criar novos métodos.

É exatamente o que acontece no Luca: há métodos JSON para as páginas AJAX, e há métodos de páginas Web para as páginas sem AJAX (utilizáveis em navegadores antigos ou incompatíveis). Funciona, mas duplica algum código e mistura conceitos.

O ideal seria haver um pré-controlador para Web, intimamente ligado ao View, que atendesse as páginas Web normais, e este por sua vez refurmulasse a requisição controlador "puro", agnóstico ao View, e implementa apenas as regras de negócio. Já as requisições JSON poderiam ir direto ao controlador "puro", pois trafegam dados puros (e podem servir a clientes GUI).

No Luca, há apenas um controlador "puro", que é responsável por comunicar-se com o celular (assim eu posso registrar minhas despesas no celular e fazer upload via Bluetooth). Por sua vez, este controlador invoca métodos de outros, pois diversas transações diferentes são levadas a cabo a cada sincronização celular-servidor. (Foi fácil chamar métodos de outros controladores pois todos já tinham suporte a AJAX).

Em resumo:

* No Turbogears e aparentemente nos demais frameworks, induz-se poluição dos Controllers devido à anemia dos Models e dos Views;

* Os Models "sugeridos" pelos frameworks são fracos pois são apenas uma casca fina sobre SQL, embora seja fácil implementar e usar Models mais adequados.

* Os Views dos frameworks são fracos pois restringem-se a encoders, como os templates XML, quando deveriam ser classes Python normais (e estas sim, por sua vez, poderiam usar algum mecanismo de template XML).

* Talvez eu esteja exigindo do Turbogears que sejam servidores de aplicação, não simples um framework Web que é afinal a sua proposta. (Mas, na minha opinião, um sistema AJAX tem características não-Web.)

* Minhas queixas contra o MVC estavam erradas, o problema dos frameworks poderia ser a má interpretação do pattern MVC.

2008/01/27

Decepção com frameworks Web

Depois de quase dois anos trabalhando no Luca, cheguei à conclusão que o Turbogears, e na verdade todos os frameworks Web da moda, são largamente inadequados para desenvolvimento de sistemas "sérios", no estilo ERP.

Os frameworks Web atuais têm, em minha opinião, o grande defeito de serem norteados por ideologia e não por necessidades reais. Alguns exemplos:

* Todos alegam ser "Model-View-Controller", porque MVC é dogma da orientação a objetos, e todo desenvolvedor sério sabe que desacoplamento total entre visão e controle é utopia, embora uma separação razoável seja saudável. Se MVC perfeito fosse perfeitamente possível, estaríamos usando para Web as mesmas linguagens e ferramentas GUI que usávamos nos anos 90 (afinal, se eram perfeitas, ainda serviriam).

A conseqüência mais visível no Luca dessa separação MVC despropositada é a duplicação de informações no código. Por exemplo, numa tela comum de cadastro, eu preciso especificar as colunas em diversos lugares diferentes:

- No modelo, em SQLObject-ês
- No template XML para AJAX
- No template XML para Web simples (sem AJAX)
- No controlador, para validar o que vem do navegador e repassar ao modelo

Eu estava trabalhando num gerador de templates XML para o meu aplicativo, de modo a diminuir a contagem de 4 para 3. Mas de repente percebi que o certo seria tomar providências para que a contagem caísse a 1. E isto provavelmente implica em usar outro framework que não o TurboGears.

Separar controle de apresentação é análogo a separar conteúdo de estilo. Quem desenvolve site sabe que HTML representa o conteúdo e CSS representa o estilo, mas é impossível separar completamente as duas coisas. Sempre é necessário mexer alguma coisa no HTML para atingir a apresentação final desejada.

* Muitos têm mapeamento objeto-relacional, porque SQL é "ruim" e deve-se usar classes ao invés da interface DBI -- até que a Revolução dos Objetos varra os bancos de dados em favor de bancos de objetos. O resultado é a perda do poder do SQL sem o ganho de poder que existiria num banco de objetos de verdade, e adicionam uma camada de software em cima do DBI, com seus próprios bugs, limitações e idiossincrasias.

Pessoalmente sofri muito com o SQLObject, que tem bugs de Unicode com o MySQL ainda não resolvidos. Qualquer consulta não trivial seria mais fácil fazer em SQL do que em SQLObject. A única possível vantagem de usar SQLObject é a portabilidade de banco de dados, mas mesmo isso é limitado. Às vezes, algo que funciona bem em MySQL não funciona em Postgres por um motivo totalmente obscuro, ou um nome de coluna que funciona no MySQL não funciona no Interbase/Firebird (e o SQLObject não resolve isso, é preciso trocar mesmo o nome da coluna).

Concluo que o ideal é tomar partido de forma clara: ou se usa SQL via interface DBI da sua linguagem predileta, ou se usa um banco de objetos nativo como ZODB. E dentre as duas opções, eu prefiro SQL, em particular se existe alguma chance de outros sistemas em outras linguagens terem de manipular esses mesmos dados. (Como um sistema escrito em Visual Basic vai acessar um banco de objetos ZODB?)

* Templates XML. É outra praga que contamina quase todos os frameworks. Uns poucos decidem ser "agnósticos a template" mas sempre deixam aberta a possibilidade de usar um template de terceiros. Nunca gostei de template XML, mas procurei aceitar a idéia durante o desenvolvimento do Luca, afinal eu poderia estar errado. Mas vejo que eu estava é certo.

XML não foi feito para ser produzido ou editado por humanos. Tanto é assim que o CSS não tem sintaxe XML, justamente porque ele será mais provavelmente editado por um designer, enquanto o HTML de conteúdo será mais provavelmente gerado por máquina e que (quase) nunca é editado por questões de estilo.

XML foi feito para representar dados passivos de forma hierárquica. Coisas como páginas dinâmicas não são bem representadas por XML. Toda linguagem de template acaba resultando em algo parecido com COBOL: uma linguagem rígida, pouco poderosa e desagradável de usar.

Para piorar, um template XML é praticamente um mapeamento de HTML, tanto que algumas linguagens alegam como "vantagem" o fato do XML ser renderizável no browser mesmo sem ser processado pelo framework. Ou seja, é praticamente inútil para renderizar qualquer coisa que não seja um HTML convencional. Isso inclui gerar um HTML mais apropriado para um dispositivo diferente do desktop comum (como sabem os designers, apenas trocar o CSS não é suficiente para tornar uma página boa para qualquer tamanho de tela).

Seria um pouco excessivo pedir que o XML de template fosse capaz de gerar interfaces diferentes de Web (e.g. interface texto ou GUI), afinal ele é justamente o View, e diferentes interfaces pedem diferentes Views. Por isso mesmo, os templates deveriam ser arquitetados de forma a ter pouca ou nenhuma informação sobre nomes de colunas e lógica de programação. Tais coisas pertencem à dupla Model e/ou Controller.

Ouso dizer que o PHP daria uma ótima linguagem de template para os frameworks. Não é uma boa linguagem de uso geral, apesar de infelizmente o PHP ser largamente usado como linguagem de desenvolvimento Web, lugar que na opinião de alguns caberia ao Javascript server-side. Mas como linguagem de template, ou seja, HTML com alguma inteligência embutida, PHP é a melhor. Eu não gosto de PHP mas uso-o no meu sítio Web, simplesmente porque as opções baseadas em Python são piores, para esse uso particular.

No gerador de templates XML que eu estava fazendo para o Luca, eu escrevia "meta templates", usando sintaxe Python convencional, mais ou menos assim:


t = SimpleTable()
t.append( TextField(name="bla", size=40) )


A idéia é usar sintaxe Python convencional, não usar XML e procurar pensar a interface num nível mais alto. O mesmo meta-template gera versões AJAX e não-AJAX da mesma página (no Luca, as duas versões podem ser consideravelmente diferentes). E poderia mesmo ser usado no futuro para gerar uma interface texto ou GUI. Na verdade, esse código poderia estar no próprio Controller ao invés de ser um template. O "View" passa a ser a biblioteca que implemente as classes SimpleTable, TextField etc.

---

Bem, os problemas estão explanados. Quais são as soluções, em particular para meu pet project, o Luca? Seria possível arrumar o Luca, usando técnicas mais adequadas, sem ter de reescrever tudo do zero? Seria possível fazer isso sem abandonar o TurboGears? Ou talvez abandonando o TurboGears mas usando CherryPy, principal componente do TurboGears, de modo a economizar reescrita?

Penso que o código do Luca está demasiadamente poluído pelos problemas do framework, e uma tentativa de reaproveitar muito código acabaria num trabalho mal-feito. Algumas partes muiti reaproveitáveis do Luca (e.g. cálculo do balanço) já são totalmente separadas dos Controllers, e desta forma foram "salvas" dos problemas do framework. Tais partes só teriam de ser reescritas se se trocasse a linguagem Python por Ruby, PHP, Javascript ou outra qualquer.

O Ruby on Rails seria uma coisa interessante de se tentar. Mas ele padece dos mesmos "pecados originais" que o TurboGears, fora que usa outra linguagem, o que significa reescrita total do Luca. Admito que o Ruby on Rails é mais bem-feito que o TurboGears, mas as benfeitorias do RoR não resolvem meu problema.

Em resumo, o framework ideal que procuro teria as seguintes características:

- Preferencialmente feito em Python;
- Não imponha mapeamento objeto-relacional;
- Não imponha templates XML
- Seja (mesmo que minimamente) aberto à possibilidade de Views diferentes de Web

Para atingir estes objetivos sem deixar de usar TurboGears, seria necessário fazer o seguinte:

- Não usar a autenticação nativa oferecida pelo TurboGears, já que este depende do mapeamento objeto-relacional;
- Modificar o código do Luca de modo a utilizar DBI ao invés de SQLObject
- Escrever um decorator @expose próprio, que gere HTML a partir dos meta-templates ao invés do Kid.

2008/01/22

Black-Scholes calculator - Javascript version

I took the Black-Scholes calculator for Maemo and converted to Javascript. The link is http://www.epx.com.br/ctb/bscalc.php.