Desenvolvimento - ASP. NET

Enxugando Controllers no ASP.NET MVC

Este artigo é voltado para as boas práticas de programação e tem como objetivo transmitir técnicas, metodologias e experiências afim de projetar melhores Controllers. Tanto do ponto de vista funcional, como arquitetural.

por Flávio Henrique de Carvalho



Qual o papel de um Controller dentro da arquitetura de um sistema ASP.NET MVC ? Segundo a definição da Microsoft, que apesar de não ser a criadora do padrão MVC é quem o implementou no ASP.NET MVC é a seguinte. “Controllers are the components that handle user interaction, work with the model, and ultimately select a view to render that displays UI. In an MVC application, the view only displays information; the controller handles and responds to user input and interaction (”. Que em linhas gerais explicita que o Controllers tem a função básica de receber as interações de usuários e exibir informações através das Views de acordo com tais interações. Ou seja, é uma ponte entre as regras de negócio, estas determinam as funcionalidades de uma aplicação e as interações do usuário que as manipulam. Eu simplificaria chamando-o de um “pegador” de entradas de usuário e um “chamador” de Views para exibição de saídas, nada mais que isto.

Indo na contramão a estes conceitos, na prática costuma-se encapsular aos Controllers as regras de negócio, engordando-os dentro das aplicações MVC. E porque engorda-se Controllers com código que não fazem parte de seu contexto? Um dos motivos é porque ignora-se o Princípio da Responsabilidade Única (SRP em inglês), que afirma que uma classe deve ter apenas um motivo para mudar, devendo fazer apenas uma única coisa. Os Controllers apesar de possuírem uma função diferenciada dentro da arquitetura MVC, também são classes por natureza e como tal devem se enquadrar no SRP, fazendo somente aquilo que foi projetado pra fazer.

Do ponto de vista da orientação objetos Controllers devem ser vinculados a outras classes através de associações. Uma associação é um vínculo que permite que objetos de uma ou mais classes se relacionem e que através deste vínculo seja possível que um objeto convoque comportamentos e estados de outros objetos. Um Controller não deve estar agregado a outras classes e nem tampouco ser composto de outras classes. Um Controller deve ser independente, contendo associações que não contenham ou sejam partes de outros objetos. Deve sim, estar associado a uma ou mais classes sem que haja comprometimento de sua existência, nem da existência de outras classes que possam estar associadas a ele. Diante disto, deve haver uma separação clara das “partes” de um Controller e das “partes” dos objetos que se associam a ele.

Do ponto de vista da lógica da aplicação o que um Controller deve representar? Uma entidade cadastral, um processo, um serviço, uma funcionalidade? A própria IDE do Visual Studio indica um caminho quando sugere um template para a adição de Action Methods (Create, Update, Delete e Details) para a construção de Controllers. Nesse caso as operações CRUD abstraídas pelos Action Methods já vem pré-construídas, sugerindo que para cada entidade cadastral da aplicação exista um Controller separadamente. Em programação não existe apenas uma forma de implementação correta para um determinado conceito, mas não seria adequado usar um Controller para ajuntar mais de uma entidade cadastral e nem para quebrá-la em partes, a não ser que houvesse algum tipo de exceção dentro do contexto do domínio da aplicação.

O uso de um mesmo Controller se dá para a representação de um conjunto de Action Methods (chamadas de Views) que possuam uma semelhança funcional ou organizacional entre si. Por exemplo, seria apropriado um mesmo Controller para representar um menu ‘Relatórios’ onde houvessem métodos de chamadas para as Views de cada relatórios. Nesse caso seria um Controller puramente organizacional, visando agrupar funcionalidades análogas numa mesma classe controladora. Também seria indicado o uso de apenas um Controller para a chamada de Views que fizessem parte de uma sequência de ações para a criação de um objeto, ou funcionalidade. Um Controller para gerir etapas de um assistente ou wizard, ou para qualquer outra implementação de processos que possuam etapas bem definidas, início, meio e fim. Tratando-se de um relacionamento de um pra um, onde um grupo de conceitos relacionados é encapsulado num mesmo Controller, formando um mesmo processo funcional. Os Controllers devem ser definidos de forma a facilitar o entendimento da aplicação, organizando o acesso a funcionalidades e objetos, ora separando, ora agrupando.

Do ponto de vista arquitetural à medida que a quantidade de comunicação e colaboração entre as classes aumentam, a complexidade do sistema também aumenta. E à medida que a complexidade aumenta, a dificuldade de implementação, teste e manutenção do software também aumenta. Acoplamento é uma medida qualitativa do grau em que as classes são conectadas entre si. Pois à medida que as classes tornam-se mais interdependentes, o acoplamento cresce, dificultando a compreensão da aplicação e futuras manutenções. Assim como qualquer outro tipo de classe, Controllers devem ser projetados com o acoplamento mais baixo possível. É fundamental sua desvinculação máxima com as classes do domínio, de forma a atingirmos um baixo acoplamento e seus benefícios.

A complexidade de determinadas implementações pode não surgir imediatamente, de forma perceptível ao desenvolvedor, costuma ir aumentando à medida que são adicionadas novas funcionalidades. Na prática, pela própria necessidade de agilidade, o método mais rápido (que nem sempre é o melhor método) é a adição ao Controller de uma referência da outra classe e a adição de mais métodos dentro da classe adicionada. Diante desta prática, alguns Controllers passam a estar associadas à varias classes e tais classes passam a possuírem métodos e campos somente para atender as necessidades de demanda do Controller. Definindo um cenário de difícil compreensão futura para o entendimento de tais associações.

Neste cenário o melhor é criar uma classe de serviço na própria camada de negócios, que esteja associada às várias classes necessárias, sendo somente ela associada ao Controller. A principal vantagem da associação do Controller com apenas uma classe (classe de serviço) ao invés de várias outras classes, está no fato da existência de um objeto especificamente construído para atender ao Controller. Objetos devem possuir membros (construtores, propriedades, métodos, etc.) voltados para a sua própria definição, sem que haja nenhum membro que fuja deste contexto. Um Controller precisa se relacionar com o domínio da aplicação (associação com suas classes) na busca de dados para a interface de usuário. Neste relacionamento surgirão métodos e propriedades que nem sempre tem haver com o próprio conceito do objeto solicitado. Daí a necessidade do serviço, para servir como recipiente para estes novos membros. A Figura 1 ilustra um exemplo desta situação onde num primeiro cenário o Controller está associado a quatro outras classes e num segundo cenário está associado a uma única classe do domínio.

Design utilizando serviços para comunicação com o Controller

Figura 1: Design utilizando serviços para comunicação com o Controller.

Do ponto de vista da codificação o código torna-se mais limpo e “entendível” quando seus Controllers possuem elementos conhecidos da linguagem e menos amarrações intricadas com objetos da aplicação. Seguindo o próprio conceito da classe Controller, todo o seu código basicamente aponta caminhos para aberturas de Views e recebimento das interações de usuários. Qualquer código muito diferente disto provavelmente está inserido fora de seu contexto. Devendo-se desconfiar de estruturas de repetições e de controles de fluxo (IF, FOR, WHILE,...) com uma aparência mais complexa. Ao olhar para um bom código fonte de um Controller deve-se vislumbrar muito claramente as “telas” abertas por ele dentro da aplicação e as “respostas” que tais telas recebem. Há somente uma exceção aceitável para inserção de um código diferente disto dentro dos Controllers, trata-se do código relacionado as consistências de entradas de usuários que opcionalmente pode vir ou não tratado dentro de um Controller. Seja através de chamadas Ajax no código HTML ou de métodos no próprio Controller, mas isto é um outro assunto (um pouco polêmico).

Num mundo perfeito um Controller teria praticamente somente alguns pequenos métodos de retorno dos tipos ContentResult, JsonResult, ActionResult, JavaScriptResult, etc. Com alguns controles de fluxo para indicar a View à ser carregada, poucas estruturas de repetição, quase nenhuma variável e nenhuma propriedade.

Do ponto de vista do conteúdo não se adiciona a Controllers métodos contendo funcionalidades do domínio, métodos retornando e recebendo objetos do domínio, para isto existem ViewModels (tema controverso) e muito menos criação destes objetos. Métodos de acesso a banco de dados, para envios de e-mails, para carregamento e leitura de arquivos. Ou seja, outras atribuições referentes a camadas de negócios (domínio) e infraestrutura.

Os Controllers são a chave para boas aplicações ASP.NET MVC e devem ser bem projetados para que nossas aplicações não caiam na armadilha da difícil manutenabilidade, ilegibilidade e consequentemente numa mortalidade antecipada.

Flávio Henrique de Carvalho

Flávio Henrique de Carvalho - Bacharel em Ciência da Computação pela universidade Paulista de Ribeirão Preto. Trabalha com desenvolvido na plataforma Microsoft à 10 anos, para o desenvolvimento de aplicações de pesquisa na área de engenharia florestal. Focado em aplicações estatísticas e processamento de grandes massas de dados.

Visite seu blog,
http://flaviohenriquedecarvalho.wordpress.com.