Esse artigo faz parte da revista .NET Magazine edição 58. Clique aqui para ler todos os artigos desta edição

 

pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #d4d0c8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0cm; BORDER-LEFT: windowtext 1pt solid; WIDTH: 431.4pt; PADDING-TOP: 0cm; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=575>

·         Boas práticas de arquitetura;

·         Aspectos de testabilidade;

·         Paradigmas de arquitetura, como DDD;

Qual a finalidade

·         Demonstrar o uso de padrões comprovados para a criação de uma aplicação de negócios durável.

Quais situações utilizam esses recursos?

·         Aplicações que tenham um tempo de vida longo, ou de alta complexidade, vão se beneficiar dos conceitos expostos no artigo, já que terão maiores chances de sucesso.

 

Resumo do DevMan

Criar aplicações ASP.Net é algo que se faz há 7 anos. Ainda assim, com freqüência encontramos aplicações desenvolvidas sem padrões, que dificultam muito sua manutenção, ou nós mesmos desenvolvemos aplicações que, ao longo do projeto, já estão engessadas. Para resolver estes e outros problemas, padrões de arquitetura e de projetos comprovados pelo mercado e pelo tempo, vêm à nossa salvação. Este artigo mostra como é possível construir uma aplicação focada nestes conceitos.

 

Qual a melhor estrutura para uma solução? Quais os melhores padrões de arquitetura para determinado cenário? Qual  estratégia de tratamento de erros utilizar em tal aplicativo? Quais design patterns (padrões de projeto) se encaixam melhor nos requisitos levantado? Estas são apenas algumas perguntas com que nos deparamos quando iniciamos o desenvolvimento de um aplicativo. São perguntas que o arquiteto de software deve responder, e, se respondidas de maneira errada, ou sem visão de futuro, podem levar ao fracasso do projeto.

Neste artigo vou demonstrar a montagem de uma aplicação em que as preocupações reais do dia a dia do negócio serão atendidas. São requisitos reais, que um artigo focado em tecnologia não consegue normalmente atender. O foco deste artigo será atender requisitos que você recebe na montagem de uma aplicação real. Alguns pontos abordados serão:

1.      Qual estratégia de mapeamento objeto/relacional utilizar;

2.      Como montar uma estratégia de tratamento de erros eficiente;

3.      Como preparar a aplicação para aceitar mudanças de maneira flexível, sem obrigar que todo o código seja revisado ou refeito, mas apenas as partes envolvidas;

Todas estas perguntas serão respondidas diante de um cenário, que vou apresentar em seguida. Como se trata de uma aplicação Web, adotaremos o Beta do ASP.Net MVC como front-end (depois você verá porque), e utilizaremos Entity Framework (EF) para obter os dados.

 

A aplicação

Construiremos uma aplicação de e-commerce, ao estilo E-Bay, onde um usuário pode colocar um produto próprio para vender, e outro usuário pode comprá-lo. Toda aplicação precisa de uma análise do negócio, ou seja, precisa definir seu domínio. Diante disso, os seguintes casos de uso serão atendidos:

1.     Cadastrar-se no site;

2.     Logar no site;

3.     Exibir produtos à venda;

4.     Cadastrar um produto para vender, sendo possível cadastrar produtos do tipo livro ou veículo;

5.     Criar uma veiculação para um produto;

6.     Pagar por uma veiculação;

7.     Exibir um produto para possível compra

8.     Fazer a proposta de compra de um produto;

9.     Exibir propostas de compra para o vendedor do produto;

10. Aceitar ou rejeitar as propostas de compras;

11. Exibir os erros da aplicação.

 

As Figuras de 1 a 3 mostram os diagramas destes casos de uso. Atente para os números dos casos de uso, porque eles serão utilizados ao longo do artigo.

 

Figura 1. Atores do sistema

 

Figura 2. Atores “Usuário” e “Comprador” e seus casos de uso

 

Figura 3. Ator “Vendedor” e seus casos de uso

 

O aplicativo funcionará da seguinte forma: o usuário acessa o aplicativo, onde vê a página inicial da aplicação, conforme Figura 4. Neste caso, note que o usuário já está logado (veja no canto superior direito na tela o login apresentado), e pode ver os produtos à venda na área principal da aplicação (caso de uso número 3), assim como os produtos que precisam ser verificados, pois têm proposta de vendas, na parte inferior da aplicação (caso de uso número 9).

 

Figura 4. Home page da aplicação

 

O usuário poderá então, cadastrar produtos. Ele o faz clicando na aba “Meus produtos”, no canto superior direito, onde a tela apresentada na Figura 5 é exibida. Note que todos os seus produtos são exibidos, possibilitando editar os produtos existentes ou cadastrar um novo (caso de uso número 4). Novamente é exibida a lista de produtos com solicitação de compra, na parte inferior da aplicação (caso de uso número 9).

 

Figura 5. Tela de produtos

 

Ao clicar no link de incluir produtos, o usuário é direcionado à tela de inclusão. Além de ser possível incluir livros, é também possível cadastrar produtos do tipo veículos. Em ambos os casos, se algum campo não for informado, ou for inválido, um erro deve ser apresentado, conforme a Figura 6, que apresenta a tela de cadastro de veículos com um erro por falta de preenchimento de campos obrigatórios. A tela de cadastro de livros é semelhante, contendo campos para editora e autor, entre outros.

 

Figura 6. Cadastrando um veículo: há campos obrigatórios

 

Após cadastrado o produto, o usuário é direcionado a uma tela onde pode criar uma veiculação para o produto (caso de uso número 5), conforme exibido na Figura 7, onde as veiculações são listadas. Note que é possível cadastrar mais de uma veiculação por produto, e que a tabela exibe os pagamentos da veiculação em uma das colunas.

É feito então o pagamento da veiculação, conforme exibido na Figura 8, onde se vê a tela de pagamento (caso de uso número 6). Neste caso trata-se de um pagamento de cartão de crédito, mas poderia ser um pagamento feito com boleto, onde a tela de pagamento é um pouco diferente. Após o pagamento o produto passa a ser exibido na home page da aplicação, na coluna apropriada.

 

Figura 7. Exibindo as veiculações e seus pagamentos

 

Figura 8. Realizando o pagamento com cartão de crédito

 

Caso um usuário se interesse por um produto, ele clica sobre o produto exibido na Figura 4, e uma tela de exibição de detalhes do produto é exibida (caso de uso número 7). Pode então solicitar a compra (caso de uso número 8), e uma tela é exibida com os dados do produto. O usuário comprador pode então clicar em um botão de confirmação de proposta de compra, e o produto é marcado para que o vendedor possa aprovar a compra. O vendedor então poderá verificar quem é o comprador e aprovar ou rejeitar a venda (caso de uso número 10) em uma tela simples com um botão de aprovar ou rejeitar a proposta.

Há ainda a possibilidade de ocorrer um erro na aplicação (caso de uso número 11). Para ver os erros logados, o usuário poderá clicar na aba de erros, no canto superior direito da tela da aplicação (veja na Figura 4), e então uma lista de erros não tratados será exibida, conforme a Figura 9. Apenas os erros não tratados, ou seja, bugs, serão reportados nesta página. Quando um bug acontecer, além da realização do log, a view padrão de exibição de erros do ASP.Net MVC será exibida.

 

Figura 9. Lista de erros

 

O login (caso de uso número 2) é feito da maneira usual do ASP.Net MVC, sem mudanças. Já o cadastro do usuário foi alterado (caso de uso número 1), conforme a Figura 10, para incluir as propriedades nome e sobrenome, que não estão presentes normalmente no ASP.Net MVC. Mais a frente veremos como fazer para armazenas essas informações, mas já adianto que utilizamos os profiles do ASP.Net, mas sem quebrar as regras de encapsulamento e mantendo um padrão de repositório.

 

Figura 10. Cadastro de usuário

 

Esse é o cenário que iremos atender. Obviamente não compreende todas as funções de um site de compra e venda profissional, mas atende as operações mais importantes de toda a aplicação, como as operações CRUD, e regras de negócios complexas. Existem interações interessantes, onde precisaremos obter objetos que dependem uns dos outros, fazer buscas de acordo com determinado critério, especificado por um objeto, e até operações que envolvem aplicativos externos ao sistema que está sendo desenvolvido, como as operações de pagamento, que devem se integrar com um aplicativo bancário.

Mais importante do que o negócio apresentado até este instante, ou as regras de negócio apresentadas a seguir, são os padrões aplicados a cada situação. Explicarei os pontos onde os padrões são aplicados na prática e porque foram escolhidos.

 

Visão de futuro

Antes de prosseguirmos na solução proposta, vamos abordar os motivos que nos levam a montar uma arquitetura mais complexa. Não seria necessário montar uma arquitetura focada em melhores práticas se acreditássemos que a solução teria somente os requisitos apresentados até aqui. Preparamos uma arquitetura fundada em padrões reconhecidos justamente porque sabemos que a aplicação não vai manter os mesmos requisitos durante toda sua existência. Na verdade, aplicativos do mundo real não mantêm os mesmos requisitos nem mesmo durante o desenvolvimento do projeto inicial, quem diria ao longo de toda sua existência.

Durante toda a vida do aplicativo, o mesmo deverá passar o teste mais difícil de todos: o tempo. A média de investimento em TI de uma empresa, cujo negócio principal não é TI, fica entre 70% e 80% para manutenção de software, e somente o restante para criação de software novos. Isso significa que a maior parte deste investimento vai na manutenção do legado. E só de ouvir a palavra “legado”, muitos já se assustam, porque muitas vezes significa código difícil de manter (às vezes impossível). É esse desafio que uma arquitetura bem montada quer vencer.

Dito isto, nas consultorias de arquitetura que realizo, sempre recomendo que os padrões sejam utilizados, mesmo para aplicações muito pequenas. É muito comum empresas ou usuários afirmarem que um aplicativo é pequeno, não vai ter muitas atribuições, não será alterado no futuro, ou ainda que terá vida curta. É também muito comum que estes aplicativos sobrevivam por muito tempo, não sejam substituídos, e, pior de tudo, cresçam desordenadamente. Já perdi a conta de quantas vezes encontrei um aplicativo, que era para ser um simples aplicativo CRUD e foi desenvolvido na base do “arrastar e soltar”, funcionando depois de anos do que era esperado, e depois de diversas atualizações, onde agora vivem regras de negócios complexas e às vezes fundamentais para o dia a dia da empresa. Não caia nesse erro.

Se este projeto fosse um projeto do mundo real eu também recomendaria que fosse desenvolvido aplicando um ciclo de desenvolvimento ágil, de forma a garantir que os requisitos seriam implementados o mais próximo possível da realidade. O tipo de arquitetura que será montada ao longo deste artigo, baseada em DDD, como veremos a seguir, e que abraça as mudanças de forma muito flexível (ao invés do mais comum: brigar com as mudanças), facilita muito o desenvolvimento no modelo ágil, e é recomendado pela maior parte dos praticantes do Domain Driven Design.

 

Camadas: Tiers e Layers

Vou começar apresentando as camadas da aplicação. O desenho das camadas está exposto na Figura 11. Em inglês o termo camada pode ser traduzido como tier, que tem significado que se aproxima mais de camadas físicas, ou como layer, que significa camadas lógicas. Neste caso, estamos vendo uma mistura das duas. Isso não é um problema uma vez que o foco do diagrama é passar uma informação, e de nada adiantaria um diagrama que focasse em tiers ou layers, mas ficasse incompleto. Diante deste foco: resolver um problema, o diagrama atende às necessidades que motivaram sua criação.

 

Figura 11. Camadas da aplicação

 

Teremos como camada de apresentação uma aplicação web, onde utilizaremos, como já foi dito, ASP.Net MVC. O banco será SQL Server, podendo ser utilizada qualquer versão a partir do SQL Server 2000. As camadas intermediárias são bibliotecas, baseadas no template Class Library do Visual Studio. Na Figura 12 você vê a estrutura de projetos do Visual Studio.

 

Figura 12. Projetos no Visual Studio

 

Monte esta estrutura de projetos no Visual Studio, lembrando que todos os projetos são baseados no template Class Library, com exceção do projeto ClienteWeb, que é baseado em ASP.Net MVC, versão beta. Os projetos de testes são baseados no Template Test Project, e são criados automaticamente pelo Visual Studio quando você clica com o botão direito sobre o código de uma classe e seleciona “Create Unit Tests...”. Para mais referência sobre testes unitários veja a série de três artigos que publiquei na .Net Magazine, da edição 53 a 55.

O projeto será baseado em Domain Driven Design (DDD), por ser uma abordagem que atenderá muito bem às necessidades propostas, como regras de negócios complexas, facilidade de manutenção e testabilidade. Na edição 57, publiquei um artigo onde introduzi o Domain Driven Design, e sugiro que você reveja o texto deste artigo para absorver melhor os conceitos que serão apresentados neste artigo, como serviços, repositórios, entidades e objetos de valor.

Conforme proposto pelo DDD, temos uma separação clara da nossa camada de domínio da aplicação. É lá que colocaremos as regras de negócios. O projeto NetMag.VendasOnline.Modelo é o que representará esta camada e está representeado em azul na Figura 11. É por lá que o desenvolvimento da aplicação será iniciado, e não pela base de dados, algo mais comum em projetos deste tipo (note que eu disse “mais comum”, e não “melhor”). Há no entanto um motivo para iniciar pelo modelo de domínio, e não de dados. Quando você começa a aplicação pelo MER, ou diretamente modelando as tabelas no banco de dados, há uma tendência de que você trabalhe focado nos dados, o que chamamos Data Driven Development, ou desenvolvimento focado em dados, e não focado no domínio (ou negócio). Os adeptos do TDD começariam pelos testes, mas o propósito aqui não é detalhar TDD, então deixaremos este assunto para outra oportunidade.

 

Nota: O desenvolvimento focado em dados é provavelmente o mais utilizado quando se fala em ASP.Net. A maioria das ferramentas do ASP.Net é focada neste paradigma de desenvolvimento, como é o caso dos Datasets e TableAdapters, que são componentes de contato direto e manipulação de dados, e do Gridview, DataList, DetailsView, e SqlDataSource, que são componentes de apresentação dos dados na interface gráfica.

 

Nota do DevMan

Test Driven Development (TDD)

O TDD é uma técnica de desenvolvimento em que os testes vêm antes do desenvolvimento dos métodos da aplicação em si. Você escreve código que vai testar classes e métodos que às vezes nem existem, sob a perspectiva de usuário da classe.

Depois de construído um teste, escreve-se o código que fará o teste passar da forma mais simples possível. Por fim, refatora-se o código escrito, tendo em mente que o teste deve continuar passando. O processo deve ser realizado a cada nova funcionalidade que for adicionada ao projeto.

Esta técnica vem ganhando adeptos a cada dia, e alguns de seus usuários muitas vezes a defendem com o mesmo fervor com que se defende um time de futebol. Vale a pena conferir.

 

A camada de tratamento de erros, apresentada em vermelho na Figura 11, e representada pela aplicação NetMag.VendasOnline.TratamentoDeErro é totalmente independente das outras camadas, para que possa ser acessado por todas elas. Ela referencia apenas o banco de dados, que é seu repositório final, ainda que tenha outro repositório de backup (utilizado em caso de falha do banco), como veremos quando falarei de tratamento de erros. Note que esta camada não referencia nem mesmo a camada de repositório do SQL Server, mas é referenciada por ela, já que erros também podem ocorrer na camada que contém os repositórios, e estes precisam ser logados da mesma forma.

Os repositórios estão separados em duas camadas: uma que fará contato com o SQL Server (projeto NetMag.VendasOnline.Repositorios.SQLServer) e outra que fará o contato com o sistema de Profiles do ASP.Net (projeto NetMag.VendasOnline.Repositorios.ProfileWeb). Ambas estão representadas em verde na Figura 11.

 

Começando o projeto: camada de domínio

Começamos a aplicação desenvolvendo a camada de domínio, como foi falado quando apresentei as camadas da aplicação. Na Figura 13 você vê o digrama de classes das principais entidades da aplicação: produto e usuário. Note que as entidades são todas representadas por interfaces, e então concretamente construídas com classes. A classe concreta do usuário está na camada de Repositório de profiles web por uma questão de separação entre as camadas que veremos adiante. Note que a entidade produto é representada concretamente pelas classes Livro e Veiculo, que nada fazem além de definir algumas propriedades extras. Na classe base Produto estão apresentados os métodos principais da entidade, como os que aceitam ou rejeitam uma venda, ou que propõem uma nova venda. O código da classe produto é apresentado na Listagem 1. Note como as regras de negócios são mantidas perfeitamente pela própria entidade, como pode ser visto no método ProporVenda (linha 24), e como a entidade não possui conhecimento algum sobre como será realizada sua persistência no banco de dados. Esse tipo de objeto é chamado de POCO, que significa Plain Old CLR Object, ou, bom e velho objeto do CLR. Os construtores são parametrizados com os valores das propriedades e, assim como as propriedades (todas implementadas com propriedades automáticas), estão ocultos, mas podem ser vistos no diagrama da Figura 13. As classes livro e veiculo não possuem comportamento específico, mas herdam o comportamento da classe base, e suas propriedades também são automáticas, e podem ser vistas também na Figura 13.

Os objetos de valor, neste caso, são os status da venda, e as categorias. O diagrama não mostra, mas as propriedades Veiculos e Livros são estáticas, e apresentam as categorias concretas, que na verdade implementam as categorias, conforme pode ser visto na Listagem 2, nas linhas 9 e 12. Note também que sobrescrevi os métodos Equals e GetHashCode, e também os operadores == e !=. Isso foi feito para que, quando um objeto de categoria seja comparado com outro com estes operadores ou com os métodos Equals o GetHasCode, eles apenas verifiquem se o Id é o mesmo, e se for, assumam como o mesmo objeto. Isso é muito comum em objetos de valor, porque não nos importamos com a instância que temos (eles são imutáveis), mas apenas com os valores (é um livro ou é um veículo). Note ainda que os objetos Livro e Veiculo são Singletons, e são compartilhados por toda a aplicação. Não precisamos criar um objeto de valor toda vez que ele for utilizado, e por isso o Singleton foi utilizado, o que acaba por poupar memória. Ainda que o benefício da memória poupada seja muito pouco hoje em dia – memória é muito barato – o padrão valeu a pena neste contexto. Não utilize um singleton a não ser que ele seja realmente válido, porque muitas vezes eles trazem problemas, como em casos em que se cruza operações multi-threading e objetos muito complexos.

Não há necessidade de apresentar a interface IUsuario, já que o diagrama a define por completo.  O código completo da aplicação encontra-se disponível no site da revista para download.

 

Figura 13. Diagrama de classes das entidades Produto e Usuário

 

Listagem 1. Entidades Produto, Livro e Veiculo

    1 namespace NetMag.VendasOnline.Modelo.Entidades

    2 {

    3     public abstract class Produto : NetMag.VendasOnline.Modelo.Entidades.IProduto

    4     {//os construtores estão ocultos

   24         public void ProporVenda(IUsuario comprador)

   25         {

   26             if (this.StatusDaVenda >= StatusDaVenda.VendaProposta)

   27                 throw new InvalidOperationException

   28                     ("O produto está vendido ou em processo de venda.");

   29             if (this.Vendedor.Id == comprador.Id)

   30                 throw new InvalidOperationException

   31                     ("O vendedor não pode ser a mesma pessoa que o vendedor.");

   32             this.Comprador = comprador;

   33             this.StatusDaVenda = StatusDaVenda.VendaProposta;

   34         }

   35 

   36         public void AceitarOuRejeitarVenda(IUsuario vendedor, bool aceitar)

   37         {

   38             if (this.StatusDaVenda != StatusDaVenda.VendaProposta)

   39                 throw new InvalidOperationException

   40                     ("O produto não tem venda proposta.");

   41             if (vendedor.Id != this.Vendedor.Id)

   42                 throw new InvalidOperationException

   43                     ("Somente o vendedor pode aceitar ou rejeitar a venda de um produto.");

   44             if (aceitar)

   45                 this.StatusDaVenda = StatusDaVenda.VendaFechada;

   46             else

   47             {

   48                 this.StatusDaVenda = StatusDaVenda.SemProposta;

   49                 this.Comprador = null;

   50             }           

   51         }

   63     }//as propriedades estão ocultas

   64 }

 

Listagem 2. Objeto de valor Categoria

    1 namespace NetMag.VendasOnline.Modelo.Entidades

    2 {

    3     public struct Categoria

    4     {

    5         public string Nome  { get; private set; }

    6         public int Id       { get; private set; }

    

    8         private static Categoria _Veiculos = new Categoria() { Id = 1, Nome = "Veiculos" };

    9         public static Categoria Veiculos

   10         { get { return _Veiculos; } }

   11         private static Categoria _Livros = new Categoria() { Id = 2, Nome = "Livros" };

   12         public static Categoria Livros

   13         { get { return _Livros;} }

   14         public override bool Equals(object obj)

   15         {

   16             if (!(obj is Categoria))

   17                 return false;

   18             return ((Categoria)obj).Nome == this.Nome;           

   19         }

   20         public static bool operator ==(Categoria objA, Categoria objB)

   21         { return objA.Equals(objB); }

   22 

   23         public static bool operator !=(Categoria objA, Categoria objB)

   24         { return !objA.Equals(objB); }

   25 

...

Quer ler esse conteúdo completo? Tenha acesso completo