.NET - Compreendendo e utilizando a arquitetura em camadas


Programar é fácil, desenvolver um software de qualidade é outra história...

Muita gente pensa que para desenvolver um sistema de software basta contratar um programador (bem baratinho, né...) ditar a eles o seus desejos e aguardar para que após algumas semanas sentado na frente do computador o sistema esteja finalmente pronto.

É claro que durante esse tempo vão surgir novas definições e novas funcionalidades a serem implementadas para deixar o sistema do jeito que ele, o patrão, precisa.

Dá para perceber que um sistema feito nesse contexto nunca vai estar realmente "pronto" pois sofrerá mudanças contínuas e constantes, sem falar é claro, na probabilidade do programador simplesmente sumir no mapa (achou quem pagava mais...) e deixar todo o trabalho, que com certeza não foi documentado, para o próximo programador de plantão; e assim caminha o desenvolvimento de software em muitas paragens por esse Brasil varonil...

É claro que existe exceções e que felizmente esse quadro tem evoluído para um modelo mais maduro e profissional com empresas e desenvolvedores atuando de forma mais eficaz.

Neste artigo eu vou novamente falar sobre o desenvolvimento de software usando a arquitetura em camadas e mostrar mais uma vez, de forma bem simples, como podemos aplicar na prática a abordagem do desenvolvimento usando a separação das responsabilidades e as boas práticas.

Muita gente pensa que todo esse conceito é puramente acadêmico e se aplica somente a aplicações corporativas e a grandes empresas, mas a complexidade dos sistemas de softwares deixou de ser um fator exclusivo do tamanho das organizações; mesmo em pequenos projetos é indispensável que a aplicação desses conceitos seja feita para que o produto final, o software, tenha qualidade e dê o retorno esperado sobre o investimento feito. Sem isso continuaremos a estar jogando dinheiro pelo ralo...

O termo arquitetura em camadas pode parecer pomposo e estar reservado a especialistas, mas quando você desenvolve um projeto organizando diferentes componentes do sistema de forma sistemática já tem a tal arquitetura em camadas. Mesmo que isso seja feito de forma intuitiva.

De forma bem simples : basta você organizar melhor os componentes do seu sistema de forma que cada um tenha uma responsabilidade definida e distinta que já tem a arquitetura em camadas.

É claro que a complexidade dessa arquitetura pode variar dependendo da complexidade do sistema e do nível de abstração que você usou para organizar os componentes do seu sistema.

Quando você organiza os componentes do sistema separando suas responsabilidades esta criando camadas que dependendo do contexto podem ser camadas lógicas(layers) ou camadas físicas(tiers).

Obs: De forma geral, o termo layer corresponde à camada lógica e o termo tier a camada física.

Assim, podemos ter muitas maneiras de organizar um sistema em camadas dividindo-o em uma, duas, três, quatro, n camadas e isso depende do objetivo e da complexidade do sistema.

Sistemas com arquitetura em uma camada (one layer)

Aplicações com uma única camada são basicamente programas autônomos simples que não utilizam uma rede e rodam apenas localmente. Nesse tipo de programa os dados residem no próprio aplicativo não existindo portanto a necessidade realizar uma sincronização de dados. Essas aplicações tem como característica o alto desempenho visto que não existe risco de falha de comunicação e apresentam uma arquitetura centralizada.

Sistemas com arquitetura em duas camadas

Uma aplicação com duas camadas não combina todas as suas funcionalidades em um único processo mas apresenta uma separação de responsabilidades. Inicialmente houve a separação da camada de apresentação com o objetivo de apresentar uma interface mais amigável. Uma típica aplicação cliente-servidor contém duas camadas separadas onde o cliente tem a responsabilidade de capturar a entrada do usuário e exibir as mensagens e o servidor é o responsável pela comunicação.

Sistemas com arquitetura em três camadas

Uma aplicação com três camadas, adiciona outra camada para a aplicação, que poderia ser na forma de um banco de dados. Pode-se pensar em um aplicativo de três camadas como uma aplicação web dinâmica, que tem uma interface de usuário, lógica de negócios, serviços e um banco de dados cada um colocados em diferente camadas, como ilustrado na figura abaixo:

Assim uma arquitetura com duas camadas separa a interface do usuário da lógica de negócios, da mesma forma que a arquitetura em três camadas separa o banco de dados da lógica de negócios.

De forma bem simples o desenvolvimento em camadas procurar dividir a funcionalidade, componentes e o código para uma aplicação, seja para web ou para desktop, em camadas distintas que podem ser delineadas da seguinte forma:

Na literatura encontram-se com frequência os termos tiers e layers, em inglês, que geralmente são traduzidos como camadas. Olhando com atenção, embora a diferença seja bem sutil, compreende-se que tiers refere-se a uma separação física dos componentes (diferentes Assemblies, DLLs, arquivos),  enquanto layers aponta para uma separação lógica dos componentes com classes distintas e diferentes espaços de nomes.
  • Layers refere-se a organização do código e dos dados;
  • Tiers geralmente se refere à distribuição do código e dos dados;
  • n-layer não implica em n-tier e vice-versa;

"Mas que vantagem Maria leva..."

Uma arquitetura em camadas apresenta as seguintes vantagens:

Uma abordagem Prática

Vamos iniciar uma abordagem prática mostrando como desenvolver uma aplicação ASP .NET usando a linguagem C# no modelo da arquitetura em 3 camadas. A arquitetura que implementada possui o seguinte desenho:


Iremos definir a arquitetura para uma aplicação web que depois poderá ser convertida em uma aplicação Windows Forms.
Para separar fisicamente as camadas iremos usar biblioteca de classes e definindo :
  • uma biblioteca de classes para a camada de negócios (BLL - Business Logic Layer)
  • uma biblioteca de classes para a camada de acesso a dados (DAL - Data Access Layer)
  • uma biblioteca de classes para a camada Comum (CUL - Common Utility Layer )

A camada de apresentação irá utilizar Web Forms da tecnologia ASP .NET.

Como ferramenta iremos utilizar o Microsoft Visual Studio Express 2012 for Web.

Dando o pontapé Inicial - Criando a solução

Vamos iniciar criando uma solução vazia ou Blank Solution.

Abra o Microsoft Visual Studio Express 2012 for Web e no menu File clique em New Project;

Selecione a opção Other Project types e clique em Visual Studio Solutions selecionando o template Blank Solution e informando o nome AplicacaoCamadas e clique no botão OK;

Com isso criaremos uma solução vazia.

Criando o projeto ASP .NET - A camada de apresentação - UI (User Interface)

No menu FILE clique em Add -> New Project

Obs: Eu renomei o projeto CamadaApresenacao para UI de forma a ficar visualmente melhor distribuído na solução.

A seguir vemos a nossa solução e o projeto Web Forms UI incluído na solução:

Nossa camada de apresentação é uma aplicação Web Forms que utiliza páginas ASP .NET . aspx, controles de usuário (.ascx), scripts, estilos css, master page, arquivos de configuração (web.config).

Todos esses recursos já foram criados e podemos alterá-los ou excluí-los. Vamos aproveitar os recursos para criar uma camada de apresentação bem simples.

Vamos criar agora as demais camadas da nossa solução. Podemos fazer isso criando 3 pastas ou incluindo 3 novos projetos do tipo Class Library à solução.

No menu FILE clique em Add -> New Project e selecione Visual C# -> Windows -> Class Library e informe o nome BLL e clique em OK;

Repita o procedimento acima e inclua os projetos DAL e CUL como projetos do tipo Class Library para representar as camadas DAL e CUL (poderiamos ter chamada essa camada de Entidades ou Model) de forma a obter a seguinte estrutura na solução proposta:

Ao lado temos a janela Solution Explorer exibindo a solução AplicacaoCamadas e os seguintes projetos:
  1. BLL - Business Logic Layer - camada de negócio
  2. DAL - Data Access Layer - camada de acesso aos dados
  3. CUL - Common Utility Layer - camada
  4. UI - User Interface (camada que foi renomeada)

Definindo o banco de dados

Vamos agora definir o banco de dados que iremos acessar em nossa aplicação. Você pode usar qualquer banco de dados : Microsoft Access, SQL Server, Oracle, etc.

Eu vou usar um banco de dados SQL Server chamado Cadastro.mdf e a tabela Clientes que possui a seguinte estrutura e dados:

Estrutura da tabela Clientes Tabela Clientes - Dados

Após criar o banco de dados Cadastro e a tabela Clientes no SQL Server Express clique no meu TOOLS -> Connect to DataBase...

Defina o servidor como .\sqlexpress (o SQL Express Local) e selecione o banco de dados Cadastro.mdf testando a conexão:

Após isso vamos incluir no arquivo web.config a string de conexão para acessar o banco de dados Cadastro.mdf no SQL Server Express Local:


<configuration>
  ...
  <connectionStrings>
    <add name="ConexaoBD" connectionString="
Data Source=.\sqlexpress;Initial Catalog=Cadastro;Integrated Security=True" providerName="System.Data.SqlClient"/>
  </connectionStrings>
.....

Referenciando os projetos

Vamos agora criar as classes relacionados com o nosso domínio em cada projeto da seguinte forma:

Agora precisamos referenciar os projetos na solução. Clique com o botão direito do mouse sobre a solução e a seguir clique em Properties;

A seguir selecione o item Project Dependencies e defina as seguintes referências entre os projetos:

  1. UI -> deve referenciar o projeto BLL e o projeto CUL;
  2. BLL -> deve referenciar o projeto DAL e o projeto CUL;
  3. DAL -> deve referenciar o projeto CUL;

Agora vamos referenciar os projetos. Selecione o projeto BLL, clique com o botão direito do mouse sobre ele e no menu suspenso selecione Add Reference;

A seguir clique no item Solution->Projects e marque os projetos CUL e DAL de forma que o projeto BLL possa enxergar esses dois projetos;

Repita o procedimento acima referenciando no Projeto DAL o projeto CUL e no projeto UI os projetos BLL, CUL e DAL conforme mostrado abaixo:

Após definir as referências vamos definir o código no arquivo ClienteDAL.cs na camada de acesso a dados DAL que acessa o string de conexão no arquivo web.config.

Para isso primeiro temos que incluir uma referência ao namespace System.Configuration no projeto DAL;

Selecione o projeto DAL e no menu PROJECT clique em Add Reference;

A seguire selecione o item Assemblies e marque o item System.Configuration clicando em OK;

Definir estas referências é necessário para que cada camada possa visualizar a respectiva camada dependente e acessar as suas classes.

Definindo o código de cada camada

1- Camada CUL - Definindo a classe Cliente

Vamos agora definir o código de cada camada iniciando pela camada CUL onde iremos definir a classe que representa o nosso domínio representando pela tabela Clientes.

Aqui podemos usar o Entity Framework para definir o modelo de entidades mas neste artigo eu vou usar apenas ADO .NET definindo a classe Cliente conforme o código abaixo:

Temos aqui uma classe POCO (Plain Old CLR Object) que define a classe Cliente. Note que a classe possui 5 propriedades que devem mapear para cada coluna da tabela Clientes.

2 - Camada DAL - A classe SQLHelper

Na camada de acesso a dados temos que definir a classe que será responsável por acessar e persistir as informações no banco de dados.

Vamos criar nesta camada uma classe SQLHelper que conterá diversos métodos para acessar e persistir os dados. Para facilitar o nosso trabalho eu vou usar uma classe SQLHelper pronta que pode ser obtida no site da Microsoft. Se você desejar pode criar a sua própria classe para acesso aos dados.

Selecione a camada DAL e no menu Project clique em Add Class e selecione o template Class e informe o nome SQLHelper.cs;

Como o código da classe é muito extenso eu vou mostrar apenas as partes do código que nos interessa :

using System.Collections;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Xml;

namespace DAL
{
    /// <summary>
    /// The SqlHelper class is intended to encapsulate high performance, scalable best practices for 
    /// common uses of SqlClient
    /// </summary>
    public sealed class SqlHelper
    {

        //Database connection strings
        public static readonly string CONN_STRING = ConfigurationManager.ConnectionStrings["ConexaoBD"].ConnectionString;
........

Aqui temos a definição da string de conexão que é obtida do arquivo web.config.

Vamos definir também alguns métodos ExecuteDataSet sobrecarregados que retornam um dataset com as informações dos clientes:

Abaixo temos o método ExecuteDataSet que iremos chamar para obter os dados dos clientes:

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the provided SqlConnection. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  DataSet ds = ExecuteDataset(conn, CommandType.StoredProcedure, "GetOrders");
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDataset(SqlConnection connection, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteDataset(connection, commandType, commandText, (SqlParameter[])null);
        }

Após essa definição vamos abrir a classe ClienteDAL e definir o código que acessa o método ExecuteDataSet da classe SQLHelper para retornar um dataset com os dados dos clientes selecionados:

using System.Data;

namespace DAL
{
    public class ClienteDAL
    {
        public DataSet getClientesSelecionados()
        {
            string sqlCommand = "select * from Cadastro where id < 5";
            DataSet dataSet = SqlHelper.ExecuteDataset(SqlHelper.CONN_STRING, CommandType.Text, sqlCommand);
            return dataSet;
        }
    }
}

No código acima definimos uma instrução SQL que será executada para selecionar os clientes com código id menor que 5. Você pode definir o critério que desejar.

3- Camada BLL - A classe ClienteBLL

Na camada BLL vamos escrever o código para a classe ClienteBLL, onde realizamos as validações que nosso caso será verificar se o id é menor ou maior do que 5, e, caso isso ocorra lançamos um exceção que deverá ser exibida pela camada de apresentação , no nosso exemplo representada pela página Default.aspx.

O código da classe CLienteBLL deve ser o seguinte:

using System;
using System.Data;
using DAL;
using CUL;


namespace BLL
{
    public class ClienteBLL
    {
        public DataTable getClientesPorID(Cliente _cliente)
        {
            ClienteDAL _clienteDAL = new ClienteDAL();
            if (ValidarID(_cliente.ID))
            {
                return _clienteDAL.getClientesSelecionados().Tables[0];
            }
            return null;
        }

        private bool ValidarID(int clienteID)
        {
            if (clienteID > 5)
            {
                throw new ApplicationException("O código do cliente deve ser menor que 5");
            }
            return true;
        }
    }
}

Note que estamos declarando os namespaces using DAL e using CUL e definindo uma regra de negócio para lançar uma exceção para ID maior que 5.

4- Camada UI - Definindo a página Default.aspx

Vamos definir a nossa camada de apresentação incluindo na página Default.aspx um controle GridView com ID=gdvClientes e formatação Brown Sugar conforme o código abaixo:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="CamadaApresentacao._Default" %>

<asp:Content runat="server" ID="FeaturedContent" ContentPlaceHolderID="FeaturedContent">
    <section class="featured">
        <div class="content-wrapper">
            <hgroup class="title">
                <h1>Camada de Apresentação</h1>
            </hgroup>
            <p>
                Macoratti .net - Quase tudo para Visual Basic, C# e ASP&nbsp; .NET</p>
        </div>
    </section>
</asp:Content>
<asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent">
    <p>
    Visite - Macoratti .net</p>
    <p>
        <asp:GridView ID="gdvClientes" runat="server" CellPadding="4" ForeColor="#333333" GridLines="None" Height="198px" Width="469px">
            <AlternatingRowStyle BackColor="White" ForeColor="#284775" />
            <EditRowStyle BackColor="#999999" />
            <FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
            <HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
            <PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
            <RowStyle BackColor="#F7F6F3" ForeColor="#333333" />
            <SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" />
            <SortedAscendingCellStyle BackColor="#E9E7E2" />
            <SortedAscendingHeaderStyle BackColor="#506C8C" />
            <SortedDescendingCellStyle BackColor="#FFFDF8" />
            <SortedDescendingHeaderStyle BackColor="#6F8DAE" />
        </asp:GridView>
        <asp:Label ID="lblErro" runat="server" Font-Bold="True" ForeColor="Red"></asp:Label>
        &nbsp;</p>
</asp:Content>

Abaixo vemos o leiaute atual da página Default.aspx:

No evento Load da página Default.aspx inclua o código a seguir:

using System;
using System.Web.UI;
using BLL;
using CUL;

namespace CamadaApresentacao
{
    public partial class _Default : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            ClienteBLL _clienteBLL = new ClienteBLL();
            Cliente _cliente = new Cliente();
            _cliente.ID = 6;
            try
            {
                gdvClientes.DataSource = _clienteBLL.getClientesPorID(_cliente);
                gdvClientes.DataBind();
            }
            catch (ApplicationException applicationException)
            {
                lblErro.Text = applicationException.Message;
            }
        }
    }
}

Neste código estamos instanciando as classes CLienteBLL() e a classe Cliente(). Da primeira iremos usar o método getClientesPorID() passando o ID do cliente, que neste primeiro momento estamos definindo como 6; da segunda iremos atribuir o ID a propriedade ID. (Note a utilização dos namespaces BLL e CUL)

Neste primeiro momento iremos obter uma exceção visto que na validação do ID do cliente na camada de negócios temos uma regra que define que para ID maior que 5 uma exceção será lançada.

Executando o projeto neste momento iremos obter o seguinte resultado:

Alterando o ID para 5 na atribuição feita no evento Load do formulário Default.aspx a exceção não irá ocorrer e iremos obter os seguintes valores:

E assim espero que você tenha compreendido como usar uma arquitetura em camadas. O exemplo usado neste artigo é bem ingênuo mas de fácil compreensão.

Vamos então conferir se as responsabilidades estão separadas em cada camada:

Pegue o projeto completo aqui: AplicacaoCamadas.zip (sem as referências)

João 3:19 E o julgamento é este: A luz veio ao mundo, e os homens amaram antes as trevas que a luz, porque as suas obras eram más.

João 3:20 Porque todo aquele que faz o mal aborrece a luz, e não vem para a luz, para que as suas obras não sejam reprovadas.

João 3:21 Mas quem pratica a verdade vem para a luz, a fim de que seja manifesto que as suas obras são feitas em Deus.

Referências:


José Carlos Macoratti