Thin Controllers, Fat Models. Essa é a resposta mais curta: Controllers Magros, Models Gordos.
Onde devo colocar a regra de negócios? No Controller ou no Model?
Muitos programadores têm essa dúvida. Principalmente se só trabalharam com sites Web ou se estão começando a entender o que significa MVC. Felizmente ninguém tem dúvidas que NENHUMA regra de negócio deve ir nas Views. Esse artigo se destina a pessoas que ainda estão aprendendo alguns dos truques do Rails, principalmente do ActiveRecord.
Além disso estamos próximos de mais um avanço de paradigma com a inclusão do modelo REST do novo Rails 1.2. Vou usar a premissa do conceito do novo Rails para ilustrar o problema da regra de negócios.
Uma das origens da dúvida vem do seguinte: imagine um pequeno sistema de Biblioteca. Teríamos tabelas como Livros, Pessoas, Livros_Pessoas, ou seja:
Agora queremos fazer um sistema que atribua livros a pessoas, então que controller usaríamos? Será que seria:
Ou será que deveria ser:
Essas coisas podem ficar esquisitas e convidam a colocar amarrações diretamente nos controllers, junto com todas as regras. Checagens como se determinada Pessoa já tem esse mesmo livro emprestado.
Mas pelo raciocínio REST do novo Rails a idéia é que deveríamos pensar em tabelas que usamos para relacionamentos many-to-many como entidades por si só, e não apenas tabelas-cola que devem ficar escondidas na forma de Livros_Pessoas, sem uma classe Model correspondente.
Segundo o novo modelo deveríamos ter as tabelas Livros, Pessoas, Emprestimos e o código seria:
Apenas com estas pequenas mudanças, as coisas de repente passam a fazer sentido: “Livros são emprestados”, “Pessoas fazem emprestimos”. Em vez de “Criar uma associação entre Livro e Pessoa” passamos a simplesmente “Criar Emprestimos”. Leia em inglês: _“um Livro has_many emprestimos”, “um Emprestimo belongs_to Pessoa”.
O objetivo disso no Rails 1.2 é outro: facilitar a criação de ActiveResources, especificamente para este artigo estou tentando matar dois coelhos com uma só cajadada: apresentar um conceito que ainda será muito discutido quando Rails 1.2 for lançado e tentar dar uma luz ao problema genérico dos Fat Controllers.
Uma outra origem da dúvida é estrutural: você tem uma classe Livro e um LivrosController. Você tem os métodos Livro#save e LivrosController#create. Parece tudo muito igual. Uma parte da dúvida é resolvida pelo próprio Rails no caso de validações. Por exemplo, alguém poderia ficar tentado a fazer algo assim:
Mas na realidade deveríamos ter algo assim:
Do ponto de vista do usuário final do web site, ambos os códigos tem o mesmo comportamento, mas do ponto de vista de código, são duas coisas completamente diferentes. Segundo o Convention over Configuration do Rails, validações de dados são responsabilidade do Model e isso fica explícito porque os métodos de validação ficam no ActiveRecord. Mas se ignorarmos isso, a maioria naturalmente colocaria a validação no controller, pois é o primeiro lugar onde vamos parar com os dados depois do usuário submeter o formulário da página.
O Model deve ser capaz não só de se auto-gerenciar no banco (operações CRUD: SELECT, INSERT, UPDATE, DELETE) como também de ter comportamento próprio, ou seja, ele deve ser uma entidade bem-comportada. Ela deve ser capaz de ter consciência de seus deveres, obrigações e capacidades.
Validar seus próprios dados é apenas uma dessas responsabilidades. Vale a pena notar, como outro exemplo, que associações has_many aceitam blocos. É onde deve ir a lógica para tratar de dados que dizem respeito à associação entre dois Models. Digamos que queremos buscar os empréstimos de um determinado livro ordenados por data, do mais recente ao mais antigo:
Isso parece suficiente, mas o que acham deste outro código:
Agora as chamadas seriam diferentes: usaríamos @livro.emprestimos.find_recentes em vez de Emprestimo.find_recentes_by_livro(1), por exemplo. O importante não é a chamada, mas a responsabilidade. Parece mais certo que a procura por empréstimos recentes de um determinado livro seja parte da associação, e não do Model. É esse tipo de decisão que Rails facilita, obviamente partindo do princípio que você investiu tempo suficiente aprendendo as APIs do ActiveRecord.
Esse tipo de conceito não é novidade, mas normalmente não vem reforçada diretamente em outros frameworks com ORM. É importante não querer fazer com ActiveRecord o que você já fazia em outros plataformas. Quanto mais do Rails for utilizado, melhor e mais claro ficará seu código. Para dar um exemplo de como o entendimento do Ruby e Rails pode nos levar longe. Vamos imaginar o seguinte:
Podemos modularizar o código. Se nossos models usam os campos padrão de timestamp do Rails (created_at, updated_at) poderíamos separar o método find_recentes anterior em um módulo. Agora teríamos o seguinte:
Nos dois models pudemos reusar o módulo FindExtensions anterior para extendê-los. E na classe Pessoa ainda poderia usar dois módulos diferentes usando includes dentro do bloco de extensão.
O importante a lembrar é: controllers devem dar sentido à navegação do usuário. São como recepcionistas que nos despacham para os diversos departamentos da empresa. Mas as recepcionistas não tomam decisões, elas devem repassar essa responsabilidade aos seus superiores, os Models. Recepcionistas podem ter várias aparências, como páginas Web, como APIs de Web Service/REST, como RSS, etc, elas apenas repassam o resultado do que lhe passaram.
Como regra geral podemos pensar assim: Controllers têm responsabilidades bem definidas: lidar com os objetos básicos de um servidor Web (request, response, session), renderizar saídas (html, rss, xml) e redirecionar para outras actions ou controllers (controlar navegação). Todo o resto é com o ActiveRecord.
Como podemos ver, a flexibilidade do Rails vai muito longe. “Se você acha que entende RoR, você não entende RoR”, nunca pare de estudá-lo.
Voltando às origens da confusão, poderíamos pensar: “parece tão simples entender as responsabilidades do Controller e do Model e ver que são diferentes”. Então, “por que a confusão”?
Orientação a objetos nunca foi um assunto fácil. Foram necessários anos de experiência de diversas pessoas, em diversas áreas, até que a didática fosse melhorada, até que material para aprendizado aparecesse em abundância. Antes disso o natural era o bom e velho código-procedural-macarrônico. Não havia distinção clara entre “classes de objetos”.

Mas há mais nisso: hoje estamos acostumados a falar em escalabilidade, diversas camadas de servidores. Tudo derivado do modelo de três camadas: banco de dados, servidor de aplicação (middlewares) e cliente (browsers). Antes o modelo monolítico não trazia essas distinções: era tudo apenas o servidor de banco de dados. É como hoje programar aplicativos em 4GL diretamente no Informix, ou em PL/SQL diretamente no Oracle.
Depois veio duas camadas: cliente e servidor. Onde colocar a lógica? Thin Client (toda a lógica no banco de dados) ou Fat Client (toda ou parte da lógica no cliente)?
Para adicionar mais lenha na fogueira, depois surgiram os middleware, servidores de aplicação. E agora? A lógica fica em um Fat Client Visual Basic? Fica em componentes COM+ no servidor de aplicação? Fica em stored procedures T-SQL no MS SQL Server?
Na Web, pelo menos a maioria não vê problema em aceitar o browser como um Thin Client (felizmente Applets não vingaram, apesar de uma insurgência recente de aplicativos exagerados em Flash). Mesmo assim a dúvida persiste: lógica no servidor de aplicação ou no banco de dados?
Ora, no mundo J2EE nascemos sabendo que, apesar de todas as dificuldades, a lógica tem que ficar em EJBs Entity Beans. Pouco depois ficou claro que deveríamos seguir o conceito de Façades com Stateless Session Beans orquestrando os Entity Beans. A seguir ficou claro que Entity Beans poderiam ser substituídos por outras camadas de ORM como Hibernate.
Paradigmas e técnicas mudam. Simplificando bastante, existem dois campos de disputa: lógica toda centralizada no banco de dados, na forma de stored procedures ou lógica toda no servidor de aplicação, em componentes distribuídos.

O segundo grupo defende que a lógica deve ficar centralizada em componentes distribuídos, em servidores de aplicação, expostos com interfaces que são padrão de mercado. Alguns anos atrás essa interface seria RMI/IIOP, atualmente é SOAP. Alguns defendem que esta forma é mais escalável, mais simples de dar manutenção. Defendem também que o acesso ao banco de dados deve ser proibido a todos: apenas esses componentes deveriam poder alterar os dados no banco e todo o resto do mundo deveria passar por esse pedágio.
Os defensores da lógica no banco vão contra-argumentar que é custoso tranferir dados via rede do banco aos componentes para processamento, que a performance é menor e que nada garante que amanhã alguém não irá acessar diretamente o banco e invalidar a camada de lógica dos componentes.
Os defensores da lógica em componentes também vão contra-argumentar que não necessariamente a mesma empresa terá a mesma marca de banco de dados, que a manutenção será dificultada pela variedade de linguagens e tecnologias diferentes de stored procedures e que a melhor coisa é manter uma camada independente de plataforma (Web Services) usando uma linguagem mais “avançada” e “conhecida” (Java).

Realmente é uma guerra onde não há vencedores. Em grandes empresas dificilmente existe uma visão homogênea e clara sobre o que fazer. No mercado vence aquele que tiver melhor relacionamento e/ou melhor preço e/ou melhor serviço, etc. Temos um eufemismo para isso: best-of-breed – uma piada, claro. Portanto é comum ver produtos de diversos fornecedores no mesmo ambiente. Cedo ou tarde eles precisão interagir entre si. Seja compartilhando o mesmo banco, seja trocando arquivos texto entre si.
Fabricantes de “soluções” lançaram produtos gerados por comitês como “Service Oriented Architecture”, “Business Process Modeling”, e uma série de outras abstrações que, na maioria dos casos, não passa de marketing para vender mais produtos e complicar mais a vida das equipes de TI – é o que chamamos de Vendor Bullshit.
Infelizmente não existe uma solução ideal. Cada caso precisa ser estudado segundo suas próprias circunstâncias. Por exemplo, uma empresa pode ter um relacionamento de longos anos com uma Oracle. Talvez para ela faça sentido manter tudo dentro da plataforma Oracle e usar servidores de aplicação Java para canais exclusivos de comunicação como Intranets.
Outra empresa talvez seja uma provedora de soluções, ou seja, ela vende soluções a outras empresas. Nesse caso talvez faça sentido colocar tudo empacotado em um servidor de aplicação que seja independente de banco de dados, dessa forma ela poderá vender a mais clientes, seja lá qual for o banco que ele tenha.
E outra ainda pode ser uma provedora de serviços online, como uma loja. Para formar parcerias com outras lojas ou serviços de logística, por exemplo, talvez faça sentido expôr suas capacidades na forma de Web Services, de forma a “facilitar” a integração.
Nunca poderemos garantir que escolhemos o “melhor” caminho, mas podemos evitar os grandes problemas. A única maneira de fazer isso é via estudo: um bom arquiteto precisa conhecer todas as camadas envolvidas (bancos de dados, servidores de aplicação, protocolos e sistemas de integração). Adaptando o que disse antes: “se você acha que entende integração, você provavelmente não entende integração”.

No caso de Rails, ele foi feito especialmente para aplicativos web escaláveis. Várias aplicações Rails não foram feitas para conversar entre si e a recomendação é que cada aplicação use um banco de dados totalmente separado. Se houver necessidade de mais de uma aplicação Rails trocar dados entre si (digamos, dados de usuários, permissões, etc.) uma das muitas soluções é criar uma aplicação Rails somente para integração, expondo os serviços como Web Services (futuramente como ActiveResources) de forma que outras aplicações possam acessar os recursos sem precisar ir diretamente ao banco.
Por isso mesmo que existe a ênfase de colocar toda a lógica nos Models. Não é no banco, não é nos Controllers (podemos ter Controllers diferentes para fins diferentes, por exemplo, um para navegação HTML, outro para Web Services, ambos consumindo os mesmos Models).
P.S.: nenhum dos códigos acima foi testado, tirei da cabeça enquanto fui escrevendo o artigo. Sugestões e correções são bem vindas
Todos os diretos reservados a RubyOnBr. Copyright RubyOnBr .
This site is powered by Radiant CMS.