Hoje o texto vai incidir sobre um dos módulos mais conhecidos da ferramenta Ruby on Rails, o ORM (mapeamento relacional de objectos) por excelência (e defeito), o mal-amado ActiveRecord.
No auge do sucesso inicial do RoR, o ActiveRecord (baseado num padrão de desenho) foi considerado um passo à frente em relação a outros ORMs de outras plataformas (Hibernate para java, por exemplo), pois fazendo uso do adágio preferido da comunidade Rails (convenção em vez de configuração), permitiu a rápida inserção de novas estruturas de dados na base de dados e o seu motor de mapeamento em memória (o modelo). Para isso faz uso de uma camada de migrações, em que os dados são inseridos na base de dados, mapeando os atributos automaticamente no modelo, não necessitando de declaração alguma. O próprio modelo mapeia directamente para a tabela com o mesmo nome na forma plural, mais uma convenção do Rails. Que quer isto dizer? Quer dizer que, se eu inserir uma nova tabela chamada actores e nessa tabela existir um campo chamado nome, então no modelo respectivo eu não preciso de declarar o campo, ele estará automaticamente disponível. Para além disso, fornece um modo de declaração limpo de associações para o modelo, validações de campos, procedimentos de retorno (callbacks), é facilmente extensível, é modular (bom, desde o Rails 3...), permite delegações, etc... um sem fim de vantagens.
Então porquê a frustração, perguntam vocês?
Pois é, se forem seguidas as convenções do ActiveRecord, não há muito de que se queixar. No entanto, toda esta limpeza da linguagem específica ao domínio esconde um perigo em potência: a distorção do significado de mapeamento relacional de objecto. Um modelo ActiveRecord, no contexto de uma aplicação Rails, é muito mais do que um simples mapeamento de dados. Também age como um modelo de negócio. E um modelo de apresentação. E uma pseudo-camada de filtragem de um pedido. E possui uma linguagem não muito clara para interagir directamente com a base de dados. Que quero eu dizer com isto? Ora vamos então por ordem:
- "Também age como um modelo de negócio"
Tudo o que é lógica associada ao nosso modelo vai encontrar espaço no modelo AR. Em vez de providenciar só o mapeamento, também é função do modelo executar operações relativas a esses mesmos dados. Aquilo que nas arquitecturas mais associadas a aplicações web em Java se chamava os modelos de negócio. Porquê aqui e não noutro lado? Porque foi convenção do Rails. E fiquemos por aqui.
- "E um modelo de apresentação"
Na arquitectura MVC das aplicações Rails não existe o conceito de vistas de objecto, geralmente modelos a que lhes somente é permitido ler, e que encapsulam geralmente lógica associada à geração da apresentação HTML. Nas aplicações Rails existe a camada dos helpers, que se destinam portanto auxiliam a renderização. É-lhes permitido fazer coisas que um modelo AR não pode, como aceder a métodos auxiliares que retornam HTML. No entanto, para muitas coisas, esses helpers usam a instância do modelo AR para certas operações, como geração de URLs REST. Ou geração de formulários HTML. A magia é muita nesta camada. Muitos de vocês podem argumentar que os modelos não são activamente usados na camada de apresentação, mas através de funções auxiliares. Mas aí eu argumento que esses modelos, quando chegam à camada de apresentação, nem têm proibição de escrita. Existe muita possibilidade para falhas dada a possibilidade de falhar. É exactamente o que eu acho que se passa neste caso. Algumas extensões para Rails já tentaram colmatar esta falha, providenciando uma representação de um modelo de acordo com o modelo das vistas (view_models, draper), mas ainda não parecem soluções maturas, principalmente a primeira, que já nem é mantida. Vamos concluir simplesmente que o Rails se esqueceu desta parte e continuar.
- "E uma pseudo-camada de filtragem de um pedido"
A camada de Controladores dispõe de declaração de filtros. No entanto, esta camada não é claramente separada da camada dos Controladores, o que faz com que se polua os controladores com declarações de filtros. Mas para mim, pior que isso é ver como, por exemplo, no seguimento da submissão de um formulário, os parâmetros são (por convenção, novamente) passados directamente para o modelo, e aí são geralmente re-ordenados, bloqueados, é verificada a atribuição em massa... funções que geralmente a camada de filtragem deveria fazer. Não, só isso, a validação dos modelos insere os erros de validação dentro do modelo (numa sub-camada), fazendo com que este forçosamente seja usado na re-renderização do formulário com mensagens de erro (aqui, voltámos ao ponto anterior). Outro exemplo interessante é a checkbox de confirmação de algo, cujo preenchimento é geralmente tratado nos modelos, pelas razões supra-citadas e porque é mais fácil para o desenvolvedor.
- "E possui uma linguagem não muito clara para interagir directamente com a base de dados"
Esta pode não ser perceptível ao início, especialmente agora que existe o ARel (o qual aprecio), mas o ActiveRecord não permite uma interacção fora-da-caixa na comunicação com a base de dados para além de pesquisas. E se eu quiser executar um stored procedure? E se a query for mais complicada? Pois, para isso existe o execute e o find_by_sql, métodos de último recurso. Resumindo e concluindo, funcionalidades específicas das bases de dados não suportadas. Outro exemplo são as funções SQL como STRLEN, entre outras. Essas têm que ir dentro de uma string criada para esse propósito. Ou seja, o AR já é limitado na medida em que não possui suporte para todos os tipos de bases de dados (como por exemplo as NoSQL). A partir daí, é sempre a acumular.
Porque são tão importantes estes pontos para mim? Se a arquitectura for fraca, a possibilidade de os desenvolvedores abusarem das possibilidades desta solução é enorme. Eu tenho testemunhado casos desses. Não é nada bonito manter código feio.
E pronto, estes são os principais pontos. De lado deixei as migrações (que se acumulam ad eternum... ver o DataMapper para uma solução interessante para o problema), e o apegamento a más práticas em relação a bases de dados, como a omissão de constrangimento de chaves estrangeiras (foreign key constraints) ou a forma como vendem estratégias como o Herança de Tabela Única (Single Table Inheritance) não fornecendo suporte para Herança por Classes de Table (Class Table Inheritance) e as associações polimórficas directamente inseridas na tabela, práticas por demais rejeitadas nas comunidades de engenheiros de base de dados. Entre outros. Fica aqui matéria para mais um texto um dia destes.
Sem comentários:
Enviar um comentário