Sie sind auf Seite 1von 16

Tentarei ser o mais genérico possível. Nos exemplos irei utilizar ASP.Net 2.

0 e acesso a dados através do


ADO.NET, mas pode ter certeza que para outros casos a idéia não irá mudar muito, seja com LINQ,
DAAB, NHIBERNATE, MVC ou qualquer outra tecnologia para acesso a dados.

Então vamos ao que interessa.

1. Introdução

Antes de começar, quero dizer que a arquitetura de um sistema vária de projeto para projeto, de
necessidade para necessidade. Mas em sua maioria para projetos de médio e grande porte, utilizo as
boas práticas de projetos de n-camadas (n-tier). Já para projetos pequenos, utilizo uma arquitetura
minimizada (a qual não irei abordar aqui).
É importante ter em mente que a organização demanda tempo. É bem mais rápido desenvolver um
projeto de maneira desorganizada do que de maneira organizada. Mas as desvantagens de criar algo
não pensado são inúmeras, como: dificuldade de manutenção, difícil refatoração, código não expressivo,
entre outras. Então é bom pensar em tudo que for fazer, pensar em cada classe, método, propriedade,
que for criar. Parece ser besteira, mas é sempre bom tomar cuidado com nomes e padrões que serão
utilizados no projeto. Mas, sem maiores delongas: "Zelar pelo código que se faz".

Em meus projetos costumo utilizar a seguinte arquitetura em camadas:


Parece ser bem simples não é? E é mesmo. Se tivermos cuidado na hora de arquitetar o sistema e se
pensarmos em todos (maioria) dos detalhes do projeto como um todo, iremos ter um sistema fácil de
dar manutenção, legível e modular.

Alguns detalhes da arquitetura:

- Devemos entender que as camadas de baixo referenciam as de cima. NUNCA devemos fazer o
processo inverso. A única camada que não referência ninguém e todos conseguem enxerga-lá são as
entidades.

- Existem objetos próprios de cada camada, por exemplo, não iremos ter objeto da Namespace
System.Data na camada da UI (Interface com o usuário).

- Devemos lembrar que objetos que facilitam nossa vida como o SqlDataSource não estão de acordo
com padrões de projetos de muitas empresas. Pelo simples fato que ao utilizá-lo estamos indo em
contra mão ao ponto que diz (que uma camada se comunica, apenas, com a imediatamente acima). Ao
adotarmos o SqlDataSource como controle para manipular dados em nossas aplicações, estamos
fazendo com que a GUI acesse diretamente o banco de dados, e na prática apenas a DAL pode acessar
nossos meios de persistência a dados.

- As camadas podem estar distribuídas em máquinas/pastas diferentes, deste que cada camada seja
uma DLL. Em outros casos as camadas podem estar dentro de nossas aplicações ASP.NET, sendo pastas
na mesma.
- Os dados devem ser passados para as classes de cima sempre como entidade, ao invés de passar “n”
parâmetros para as camadas de cima, criando assim uma dependência entre camadas. Caso eu adicione
um atributo a mais em uma entidade, vou ter que adicionar um parâmetro a mais em meu método. Por
exemplo: Não utilize esse tipo de método em seu código:

public void InserirPessoaJuridica(string nome, string CPF, int idade);

Bem mais simples passar apenas um objeto do que passar “9271823182” de parâmetros.

public void InserirPessoaJuridica(PessoaJuridica pessoaJuridica);

OBS: O problema de se colocar todas as camadas em um único projeto é se um dia desejarmos criar
aquela mesma aplicação para outro tipo de dispositivo. Por exemplo: Temos uma aplicação ASP.NET e
desejamos agora criar uma opção para o usuário acessá-la via dispositivo móvel. Caso as camadas
estejam em um único projeto, será bem trabalhoso de fazer isto. Já no caso de cada camada ser um
projeto diferente, teremos apenas, que criar uma nova camada de “UI” e fazer com que esta referencie
as outras camadas.

Outros detalhes serão discutidos no decorrer deste artigo. Agora vamos falar sobre cada camada em
particular.

1.1 GUI

É a camada de interface com o usuário. No nosso caso é nossa aplicação ASP.NET. Poderia ser um
projeto para dispositivo móvel, Windows Form, WPF, Silverlight.

O ideal é que nessa camada encontremos apenas código que estejam diretamente relacionados com
nossas páginas aspx.

Já vi desenvolvedores de empresas grandes que faziam coisas esdrúxulas como acessar o bando direto
da GUI. Além de deixar o código seboso, o trabalho de refazer o mesmo código inúmeras vezes será
enorme.

Então vamos seguir a regra, cada camada só enxerga a imediatamente acima.

1.2 Entidade
É a única camada que todos enxergam. Esta entidade contém nossas classes básicas (Ex: Pessoa,
Médico, Casa, Cachorro, PessoaJuridica, PessoaFisica...). Classes que representam coisas do nosso
mundo real.

1.3 Façade

É um padrão faz com que tenhamos todas as funções do sistema em uma única classe. Uma possível
implementação para este padrão é utilizar a palavra chave “partial” para ter vários arquivos físicos, mas
que no final das contas será uma única classe. A finalidade disto é evitar ter um único arquivo enorme.

OBS: Esta camada é optativa. Você pode acessar diretamente a camada de negócio. Costumo utilizar
este padrão em casos onde preciso executar chamadas consecutivas ao banco em uma única operação.
Por exemplo, no caso de uma transferência de conta bancária. Preciso retirar de uma pessoa e colocar
na conta de outra. Então a façade serviria para fazer a chamada ao devidas funções.
Teria o método “Transferência” na classe “Façade”, que este por sua vez iria ter o seguinte trecho de
código:

PessoaBus.Depositar(x);
PessoaBus.Sacar(x);

Ou seja utilizo essa classe apenas quando dou preferência a ter um local de fácil acesso que faça as
operações na ordem de execução.

1.4 Business Tier

Essa camada contém tudo que for lógica de negócio. Ela que irá fazer verificações e validações dos
dados vindos e que ainda vão para o banco.

É importante saber a diferencia entre regras de negócio e validações básicas. Pois existem validações
que não precisam ser feitas necessariamente nesta classe. Como por exemplo, se o CPF é válido, pois
essa é uma regra geral, e não de um específico sistema. A validação do CPF pode ser feita tanto do lado
do cliente (javascript) como na camada de entidades.

1.5 DAL

Única camada que acessa a dados. Esta camada é especifica para isso e nada mais. Então é uma boa
prática evitar colocar validações nesta classe ou qualquer trecho de código que não esteja diretamente
relacionado com acesso a dados.
Em muitos projetos tento criar camadas de acessos a dados genéricos. Mas isso não é obrigatório
(dependendo do projeto, lógico). Se você sabe que aquele projeto sempre vai acessar apenas a um
banco de dados (Sql Server, Oracle, Mysql, Postgres ou qualquer outro) e não tem a menor chance de
mudar, então não existe a necessidade de criar uma camada genérica.

OBS: Criar os repositórios para cada entidade e mapear cada tabela do banco em uma entidade dá
muito trabalho. Então existem várias ferramentas que fazem esse trabalho para nós. São as ferramentas
de mapeamento objeto relacionais. Temos em .NET como exemplo: Nhibernate, SubSonic, LINQ. Temos
um projeto da Microsoft que abstrai o acesso a banco de dados de nossa aplicação. É um projeto
grande, que nos fornece muitas utilidades, que é a Microsoft Data Access Application Block.
Caso você opte por utilizar alguma dessas ferramentas tudo bem. Mas muito cuidado ao escolher
alguma delas. Pois alguns códigos gerados podem ir contra os padrões adotados em seu projeto, o que
pode dar muita dor de cabeça ao tentar modificar o código gerado pela mesma.

Quanto a conceitos de camadas, o que posso dizer é isso. Aconselho a estudar coisas relacionadas a
“design partner”. Com bom conhecimento de padrões de projetos e bom conhecimento da regra de
negócio da aplicação você será capaz de escrever aplicações bem arquitetadas e modeladas.

2. Codificando

Como exemplo, vou mostrar um cadastro de pessoa bem simples. Apenas para dar idéia de como seria
um sistema em camadas que costumo utilizar em alguns projetos pessoais. Porém, longe de ser o
modelo ideal para suas aplicações. Para chegar a um modelo “ideal” para A aplicação procuro me sentar
com toda a equipe de desenvolvimento (se não apenas os mais experientes) para discutirmos o modelo
de nossa aplicação e de algumas classes.

Para este exemplo, irei fazer uma aplicação ASP.NET e C# 2.0, acessando os dados com ADO.NET.
Fazendo tudo isto na mão, sem o auxílio de ferramenta alguma.

Bem, em meus projetos costumo começar de cima para baixo, ou seja, crio as camadas na seguinte
sequência: Entidade -> DAL -> Business -> Façade -> GUI. Pois me faz pensar primeiramente no
problema como um todo, e me faz refletir sobre as possíveis operações e necessidade do sistema.

2.1 Modelando o banco.

O banco será bem simples, terá apenas a tabela pessoa.


O script para a tabela é:

CREATE TABLE [dbo].[Pessoa](


[id] [int] IDENTITY(1,1) NOT NULL,
[nome] [nvarchar](100) NULL,
[dataNascimento] [smalldatetime] NULL,
[sexo] [nchar](1) NULL,
[email] [nvarchar](50) NULL,
[cpf] [nvarchar](15) NULL,
CONSTRAINT [PK_Pessoa] PRIMARY KEY CLUSTERED
)

2.2. Entidade

A camada de entidade é básica de se fazer, é basicamente um mapeamento das tabelas do banco. Para
cada tabela do banco, teremos uma classe na camada de entidades.

1: public class Pessoa


2: {
3: #region Atributos
4:
5: private DateTime _dateNascimento;
6:
7: #endregion
8:
9: #region Construtores
10:
11: public Pessoa()
12: {
13:
14: }
15:
16: public Pessoa(int id)
17: {
18: this.ID = id;
19: }
20:
21: public Pessoa(string nome, DateTime dataNascimento, char sexo, string email, string cpf)
22: {
23: this.Nome = nome;
24: this.DataNascimento = dataNascimento;
25: this.Sexo = sexo;
26: this.Email = email;
27: this.Cpf = cpf;
28: }
29:
30: public Pessoa(int id, string nome, DateTime dataNascimento, char sexo, string email, string cpf)
31: : this(nome, dataNascimento, sexo, email, cpf)
32: {
33: this.ID = id;
34: }
35:
36: #endregion
37:
38: #region Propriedades
39:
40: public int ID { get; set; }
41:
42: public string Nome { get; set; }
43:
44: public DateTime DataNascimento
45: {
46: get
47: {
48: return this._dateNascimento;
49: }
50: set
51: {
52: if (value > DateTime.Now)
53: {
54: throw new DataInvalidaException();
55: }
56: else
57: {
58: this._dateNascimento = value;
59: }
60: }
61: }
62:
63: public char Sexo { get; set; }
64:
65: public string Email { get; set; }
66:
67: public string Cpf { get; set; }
68:
69: #endregion
70: }
Dá para notar que na propriedade “DataNascimento” é feita uma validação. Essa validação pode ser
feita nesta camada, pelo fato de ser uma validação básica, onde não muda de sistema para sistema. Ou
seja, em todo local do mundo a data de nascimento de uma pessoa vai ser menos que a data atual.

2.3. Camada de acesso a dados

Para criar as camadas de acesso a dados, costumo criar uma (ou mais) interfaces ou classes abstratas
que servirão como base criar as classes de acesso a dados de cada entidade. Esta classe ou interface
contém definições de métodos básicos, que sei que todas as classes vão conter, como por exemplo:
Inserir, Atualizar, Recuperar por ID, Deletar.

Para o exemplo, irei utilizar a seguinte interface:

1: /// <summary>
2: /// Interface com todos os métodos necessários para uma classe de DAO
3: /// </summary>
4: /// <typeparam name="T">tipo do objeto que será manipulador pela DAO</typeparam>
5: public interface IDataAccessObject<T> where T : new()
6: {
7: T Get<K>(K id);
8:
9: void Insert(T obj);
10:
11: void Update<K>(K id, T obj);
12:
13: void Delete<K>(K id);
14: }

Esta é uma interface genérica que contém métodos básicos para outras classes. Alguns métodos são
genéricos por recebem o tipo da chave primária da entidade (int, long, short).

Outra classe da DAL que costumo utilizar é uma classe para auxiliar coisas como: criação de parâmetros,
criação de comandos, entre outras operações. Como falei anteriormente, você pode utilizar a Microsoft
Data Access Application Block para ajudar com esse tipo de coisas. Essa biblioteca contém inúmeros
métodos.

Para a aplicação de demonstração, irei utilizar uma classe bem simples que fiz apenas para esse
exemplo. Detalhe que não estou abstraindo o banco da aplicação, mas aconselho você a criar uma
classe que abstraia o banco que esta se utilizando. Outra observação é que está classe está incompleta,
fiz apenas para exemplo mesmo. Esta é uma classe que tem que ser bem pensada e bem trabalhada
com a finalidade de obter melhor desempenho do banco de dados que está usando. Garanto que não
será difícil modificar esta classe para ser genérica. Segue o código da mesma:

1: public class DatabaseHelper


2: {
3: #region Propriedades
4:
5: public SqlConnection MyBdConnection { get; set; }
6: public string NomeStringConexao { get; set; }
7:
8: #endregion
9:
10: #region Construtores
11:
12: public DatabaseHelper()
13: {
14: this.NomeStringConexao = "DefaultStringConexao";
15: this.MyBdConnection = new SqlConnection(this.NomeStringConexao);
16: }
17:
18: public DatabaseHelper(string nomeStringConexao)
19: {
20:
21: this.NomeStringConexao = nomeStringConexao;
22: this.MyBdConnection = new SqlConnection(this.NomeStringConexao);
23: }
24:
25: #endregion
26:
27: #region Métodos Privados
28:
29: private string GetCorrectParameterName(string parameterName)
30: {
31: if (parameterName[0] != '@')
32: {
33: parameterName = "@" + parameterName;
34: }
35: return parameterName;
36: }
37:
38: #endregion
39:
40: #region Métodos Públicos
41:
42: public static DatabaseHelper Create()
43: {
44: return new DatabaseHelper();
45: }
46:
47: public static DatabaseHelper Create(string nomeStringConexao)
48: {
49: return new DatabaseHelper(nomeStringConexao);
50: }
51:
52: public void OpenConnection()
53: {
54: if (this.MyBdConnection.State == System.Data.ConnectionState.Closed)
55: {
56: this.MyBdConnection.Open();
57: }
58: }
59:
60: public void CloseConection()
61: {
62: this.MyBdConnection.Close();
63: }
64:
65: public SqlParameter BuildParameter(string nome, object valor, DbType tipo, int size)
66: {
67: SqlParameter parametro = new SqlParameter(this.GetCorrectParameterName(nome), valor);
68: parametro.DbType = tipo;
69: parametro.Size = size;
70: return parametro;
71: }
72:
73: public void BuildParameter(string nome, object valor, DbType tipo, int size, List<SqlParameter>
listParametros)
74: {
75: SqlParameter parametro = this.BuildParameter(nome, valor, tipo, size);
76: listParametros.Add(parametro);
77: }
78:
79: public SqlParameter BuildOutPutParameter(string nome, DbType tipo, int size)
80: {
81: SqlParameter parametro = new SqlParameter();
82: parametro.ParameterName = this.GetCorrectParameterName(nome);
83: parametro.DbType = tipo;
84: parametro.Size = size;
85: parametro.Direction = ParameterDirection.Output;
86: return parametro;
87: }
88:
89: public void BuildOutPutParameter(string nome, DbType tipo, int size, List<SqlParameter>
listParametros)
90: {
91: SqlParameter parametro = this.BuildOutPutParameter(nome, tipo, size);
92: listParametros.Add(parametro);
93: }
94:
95: public void ExecuteNonQuery(SqlCommand command)
96: {
97: command.ExecuteNonQuery();
98: }
99:
100: public void ExecuteNonQuery(SqlCommand command, bool openConnection)
101: {
102: if (openConnection)
103: {
104: this.OpenConnection();
105: }
106: this.ExecuteNonQuery(command);
107: if (openConnection)
108: {
109: this.CloseConection();
110: }
111: }
112:
113: public void ExecuteNonQuery(string query, params SqlParameter[] parameters)
114: {
115: Exception erro = null;
116: try
117: {
118: this.OpenConnection();
119: SqlCommand command = this.MyBdConnection.CreateCommand();
120: command.CommandText = query;
121: command.Parameters.AddRange(parameters);
122: this.ExecuteNonQuery(command);
123: this.CloseConection();
124: }
125: catch (Exception ex)
126: {
127: erro = ex;
128: }
129: finally
130: {
131: this.CloseConection();
132: }
133:
134: if (erro != null)
135: {
136: throw erro;
137: }
138: }
139:
140: public void ExecuteCommands(params SqlCommand[] commands)
141: {
142: Exception erro = null;
143: SqlTransaction trans = null;
144: try
145: {
146: this.MyBdConnection.Open();
147: trans = this.MyBdConnection.BeginTransaction();
148: for (int i = 0; i < commands.Length; i++)
149: {
150: commands[i].Transaction = trans;
151: this.ExecuteNonQuery(commands[i]);
152: }
153: trans.Commit();
154: this.MyBdConnection.Close();
155: }
156: catch(Exception ex)
157: {
158: trans.Rollback();
159: erro = ex;
160: }
161: finally
162: {
163: this.MyBdConnection.Close();
164: }
165:
166: if (erro != null)
167: {
168: throw erro;
169: }
170: }
171:
172: #endregion
173: }

Para criar essa classe auxiliar deve se considerar muitas coisas:

- Abrangência;

-Desempenho;

- Utilização de cachê ou não;

- Objetos que serão utilizados para manipular os dados (DataDet, DataTable, SqlDataReader);

- Se será utilizado apenas stored procedure ou se todas as querys vão estar na aplicação.

- ...;

Já temos uma interface para nossa camada de acesso a dados e uma classe que irá nos auxiliar com a
mesma. Agora nos resta criar as classe de acesso para cada entidade presente em nosso projeto. O
código da nossa classe Pessoa será o seguinte:

1: public class PessoaDAO : IDataAccessObject<Pessoa>


2: {
3: #region Atributos
4:
5: private DatabaseHelper databaseHelper;
6:
7: #endregion
8:
9: #region Construtores
10:
11: public PessoaDAO()
12: {
13: databaseHelper = DatabaseHelper.Create();
14: }
15:
16: #endregion
17:
18: #region IDataAccessObject<Pessoa> Members
19:
20: public Pessoa Get<K>(K id)
21: {
22: Pessoa pessoa = new Pessoa();
23: SqlDataReader reader = null;
24: try
25: {
26: string query = "SELECT * FROM Pessoa WHERE id = @id";
27: databaseHelper.OpenConnection();
28: reader = databaseHelper.ExecuteDataReader(query,
29: new SqlParameter("id", id));
30: while (reader.Read())
31: {
32: pessoa.Nome = reader["nome"].ToString();
33: pessoa.Cpf = reader["cpf"].ToString();
34: pessoa.DataNascimento = Convert.ToDateTime(reader["dataNascimento"]);
35: pessoa.Email = reader["email"].ToString();
36: pessoa.ID = Convert.ToInt32(reader["nome"]);
37: }
38: reader.Close();
39: this.databaseHelper.CloseConection();
40: }
41: finally
42: {
43: if (reader != null)
44: {
45: reader.Close();
46: }
47: this.databaseHelper.CloseConection();
48: }
49: return pessoa;
50: }
51:
52: public void Insert(Pessoa obj)
53: {
54: string query = "INSERT INTO PESSOA (nome, dataNacimento, cpf, email, sexo) VALUES (@nome,
@dataNascimento, @cpf, @email, @sexo)";
55: this.databaseHelper.ExecuteNonQuery(query,
56: new SqlParameter("nome", obj.Nome),
57: new SqlParameter("dataNascimento", obj.DataNascimento),
58: new SqlParameter("cpf", obj.Cpf),
59: new SqlParameter("email", obj.Email),
60: new SqlParameter("sexo", obj.Sexo));
61: }
62:
63: public void Update<K>(K id, Pessoa obj)
64: {
65: throw new NotImplementedException();
66: }
67:
68: public void Delete<K>(K id)
69: {
70: throw new NotImplementedException();
71: }
72:
73: #endregion
74: }

Simples não é?! A implementação dos métodos de “delete” e “update”, eu deixo como exercício para
você. :D

2.3. Camada de Negócio

A idéia da camada de negócio é bastante simples. Como falei anteriormente, ela tem o papel de acessar
a camada de dados e é quem fará validações em cima da mesma. Geralmente, assim como a camada de
DAL, costumo criar uma interface nessa camada com os métodos mais utilizados. Mas para exemplo irei
mostrar apenas o método “insert” da classe de negócio de pessoa.

1: public class PessoaBus


2: {
3: private PessoaDAO pessoaDAO;
4:
5: public PessoaBus()
6: {
7: this.pessoaDAO = new PessoaDAO();
8: }
9:
10: public void Inserir(Pessoa pessoa)
11: {
12: //caso haja validação, ela pode ser feita neste método mesmo.
13: this.pessoaDAO.Insert(pessoa);
14: }
15: }

Um padrão que costumo utilizar nessa camada são as factories. Que tem o papel de criar instâncias das
classes. Ou seja, com esse padrão eu consigo ter acesso a qualquer objeto acessando apenas uma classe.
Exemplo de Factory:

1: public static class FactoryDAO


2: {
3: public static ContatoDAO CreateContatoDAO()
4: {
5: return new ContatoDAO();
6: }
7:
8: public static ContatoDAO CreatePessoaDAO()
9: {
10: return new PessoaDAO();
11: }
12: }

2.4. Façade Tier

Façade é simples o bastante que dispensa palavras. Então vamos ao exemplo:

Arquivo 1: Facade.cs

1: public partial class Facade


2: {
3: private PessoaDAO _pessoaDAO;
4: private ContatoDAO _contatoDAO;
5:
6: public Facade()
7: {
8: _contatoDAO = FactoryDAO.CreateContatoDAO();
9: _pessoaDAO = FactoryDAO.CreatePessoaDAO();
10: }
11: }

Arquivo 2: PessoaFacade.cs

1: public partial class Facade


2: {
3: public void InserirPessoa(Pessoa pessoa)
4: {
5: this._pessoaDAO.Insert(pessoa);
6: }
7:
8: public void UpdatePessoa(int id, Pessoa pessoa)
9: {
10: this._pessoaDAO.Update<int>(id, pessoa);
11: }
12:
13: public Pessoa GetPessoa(int id)
14: {
15: return this._pessoaDAO.Get<int>(id);
16: }
17:
18: public void DeletePessoa(int id)
19: {
20: this._pessoaDAO.Delete<int>(id);
21: }
22: }

OBS: Notar o uso da palavra chave “partial”.

2.5. UI Tier

Não irei mostrar exemplo de código desta camada. Pois este é o nosso projeto ASP.NET que já sabemos.
A única coisa que devemos fazer nessa camada é encapsular os dados no objeto Pessoa, por exemplo e
em seguida passar o mesmo para algum método da Façade.

Bem, espero ter ajudado. O básico de uma boa arquitetura de sistema é isto. Pode ter certeza que lendo
esse material, depois de ter entendido o mesmo por completo e depois ler profundamente sobre
padrões de projetos você estará totalmente apto para criar uma aplicação bem modelada e arquitetada.

Em outra oportunidade escreverei um pouco mais sobre arquitetura em camadas e trarei exemplos de
sistemas bem arquitetados.

Abraços.

Das könnte Ihnen auch gefallen