Você está na página 1de 20

Arquitetura em Camadas com C#

Aprenda boas prticas de como modelar e arquitetar suas aplicaes c# em camadas.


8

Gostei (6)
(1)

Verso em pdf desse artigo pode ser encontrada no seguinte link: http://cid-2d6d3503299ba131.skydrive.live.com/self.aspx/Artigos/Felipe%20Pimentel%20ArqCamadas.pdf

Se eu fosse falar "tudo" de arquitetura de projetos, eu acho que passaria muito tempo tentando explicar uma coisa que, talvez, no ficasse to claro. Ento, vou ser um pouco mais prtico. Tentarei ser o mais genrico possvel. No entando, nos exemplos irei utilizar C# 2.0 e acesso a dados atravs do ADO.NET. Mas pode ter certeza que idependente da tecnologia que voc escolher, a idia deste artigo ir se aplicar, seja com LINQ, DAAB, NHIBERNATE, MVC ou qualquer outra tecnologia. Ento vamos ao que interessa. 1. Introduo Antes de comear, quero dizer que a arquitetura de um sistema vria de projeto para projeto, de necessidade para necessidade. Mas em sua maioria para projetos de mdio e grande porte, utilizo as boas prticas de projetos de n-camadas (n-tier). J para projetos pequenos, utilizo uma arquitetura minimizada (a qual no irei abordar aqui). importante ter em mente que a organizao demanda tempo. bem mais rpido desenvolver um projeto de maneira desorganizada do que de maneira organizada. Mas as desvantagens de criar algo no pensado so inmeras, como: dificuldade de manuteno, difcil refatorao, cdigo

no expressivo, entre outras. Ento bom pensar em tudo que for fazer, pensar em cada classe, mtodo, propriedade, que for criar. Parece ser besteira, mas sempre bom tomar cuidado com nomes e padres que sero utilizados no projeto. Mas, sem maiores delongas: "Zelar pelo cdigo que se faz". Em meus projetos costumo utilizar a seguinte arquitetura em camadas:

Parece ser bem simples no ? 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 fcil de dar manuteno, legvel 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 no referncia ningum e todos conseguem enxerga-l so as entidades. - Existem objetos prprios de cada camada, por exemplo, no iremos ter objeto da Namespace System.Data na camada da UI (Interface com o usurio). - Devemos lembrar que objetos que facilitam nossa vida como o SqlDataSource no esto de acordo com padres de projetos de muitas empresas. Pelo simples fato que ao utiliz-lo estamos indo em contra mo 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 aplicaes, estamos fazendo com que a GUI acesse diretamente o banco de dados, e na prtica apenas a DAL pode acessar nossos meios de persistncia a dados.

- As camadas podem estar distribudas em mquinas/pastas diferentes, deste que cada camada seja uma DLL. Em outros casos as camadas podem estar dentro de nossas aplicaes ASP.NET, sendo pastas na mesma. - Os dados devem ser passados para as classes de cima sempre como entidade, ao invs de passar n parmetros para as camadas de cima, criando assim uma dependncia entre camadas. Caso eu adicione um atributo a mais em uma entidade, vou ter que adicionar um parmetro a mais em meu mtodo. Por exemplo: No utilize esse tipo de mtodo em seu cdigo: public void InserirPessoaJuridica(string nome, string CPF, int idade); Bem mais simples passar apenas um objeto do que passar 9271823182 de parmetros. 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 aplicao para outro tipo de dispositivo. Por exemplo: Temos uma aplicao ASP.NET e desejamos agora criar uma opo para o usurio acess-la via dispositivo mvel. 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 sero discutidos no decorrer deste artigo. Agora vamos falar sobre cada camada em particular.

1.1 GUI a camada de interface com o usurio. No nosso caso nossa aplicao ASP.NET. Poderia ser um projeto para dispositivo mvel, Windows Form, WPF, Silverlight. O ideal que nessa camada encontremos apenas cdigo que estejam diretamente relacionados com nossas pginas aspx. J vi desenvolvedores de empresas grandes que faziam coisas esdrxulas como acessar o bando direto da GUI. Alm de deixar o cdigo seboso, o trabalho de refazer o mesmo cdigo inmeras vezes ser enorme. Ento vamos seguir a regra, cada camada s enxerga a imediatamente acima.

1.2 Entidade a nica camada que todos enxergam. Esta entidade contm nossas classes bsicas (Ex: Pessoa, Mdico, Casa, Cachorro, PessoaJuridica, PessoaFisica...). Classes que representam coisas do nosso mundo real.

1.3 Faade um padro faz com que tenhamos todas as funes do sistema em uma nica classe. Uma possvel implementao para este padro utilizar a palavra chave partial para ter vrios arquivos fsicos, 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 negcio. Costumo utilizar este padro em casos onde preciso executar chamadas consecutivas ao banco em uma nica operao. Por exemplo, no caso de uma transferncia de conta bancria. Preciso retirar de uma pessoa e colocar na conta de outra. Ento a faade serviria para fazer a chamada ao devidas funes. Teria o mtodo Transferncia na classe Faade, que este por sua vez iria ter o seguinte trecho de cdigo: PessoaBus.Depositar(x); PessoaBus.Sacar(x); Ou seja utilizo essa classe apenas quando dou preferncia a ter um local de fcil acesso que faa as operaes na ordem de execuo.

1.4 Business Tier Essa camada contm tudo que for lgica de negcio. Ela que ir fazer verificaes e validaes dos dados vindos e que ainda vo para o banco. importante saber a diferencia entre regras de negcio e validaes bsicas. Pois existem validaes que no precisam ser feitas necessariamente nesta classe. Como por exemplo, se o CPF vlido, pois essa uma regra geral, e no de um especfico sistema. A validao 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. Ento uma boa prtica evitar colocar validaes nesta classe ou qualquer trecho de cdigo que no esteja diretamente relacionado com acesso a dados. Em muitos projetos tento criar camadas de acessos a dados genricos. Mas isso no obrigatrio (dependendo do projeto, lgico). Se voc sabe que aquele projeto sempre vai acessar apenas a um banco de dados (Sql Server, Oracle, Mysql, Postgres ou qualquer outro) e no tem a menor chance de mudar, ento no existe a necessidade de criar uma camada genrica. OBS: Criar os repositrios para cada entidade e mapear cada tabela do banco em uma entidade d muito trabalho. Ento existem vrias ferramentas que fazem esse trabalho para ns. So 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 aplicao. 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 cdigos gerados podem ir contra os padres adotados em seu projeto, o que pode dar muita dor de cabea ao tentar modificar o cdigo 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 padres de projetos e bom conhecimento da regra de negcio da aplicao voc ser capaz de escrever aplicaes bem arquitetadas e modeladas. 2. Codificando Como exemplo, vou mostrar um cadastro de pessoa bem simples. Apenas para dar idia de como seria um sistema em camadas que costumo utilizar em alguns projetos pessoais. Porm, longe de ser o modelo ideal para suas aplicaes. Para chegar a um modelo ideal para A aplicao procuro me sentar com toda a equipe de desenvolvimento (se no apenas os mais experientes) para discutirmos o modelo de nossa aplicao e de algumas classes. Para este exemplo, irei fazer uma aplicao ASP.NET e C# 2.0, acessando os dados com ADO.NET. Fazendo tudo isto na mo, sem o auxlio de ferramenta alguma. Bem, em meus projetos costumo comear de cima para baixo, ou seja, crio as camadas na seguinte sequncia: Entidade -> DAL -> Business -> Faade -> GUI. Pois me faz pensar primeiramente no problema como um todo, e me faz refletir sobre as possveis operaes 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 bsica 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: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: cpf) 22: 23: 24: 25: 26: 27: 28: 29: 30: public Pessoa(int id, string nome, DateTime dataNascimento, char sexo, string email, } { this.Nome = nome; this.DataNascimento = dataNascimento; this.Sexo = sexo; this.Email = email; this.Cpf = cpf; public Pessoa(string nome, DateTime dataNascimento, char sexo, string email, string } public Pessoa(int id) { this.ID = id; } public Pessoa() { #region Construtores #endregion private DateTime _dateNascimento; #region Atributos

string cpf) 31: 32: 33: { this.ID = id; : this(nome, dataNascimento, sexo, email, cpf)

34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: }

#endregion

#region Propriedades

public int ID { get; set; }

public string Nome { get; set; }

public DateTime DataNascimento { get { return this._dateNascimento; } set { if (value > DateTime.Now) { throw new DataInvalidaException(); } else { this._dateNascimento = value; } } }

public char Sexo { get; set; }

public string Email { get; set; }

public string Cpf { get; set; }

#endregion

D para notar que na propriedade DataNascimento feita uma validao. Essa validao pode ser feita nesta camada, pelo fato de ser uma validao bsica, onde no 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 serviro como base criar as classes de acesso a dados de cada entidade. Esta classe ou interface contm definies de mtodos bsicos, que sei que todas as classes vo conter, como por exemplo: Inserir, Atualizar, Recuperar por ID, Deletar. Para o exemplo, irei utilizar a seguinte interface: 1: /// 2: /// Interface com todos os mtodos necessrios para uma classe de DAO 3: /// 4: /// tipo do objeto que ser manipulador pela DAO 5: public interface IDataAccessObject where T : new() 6: { 7: T Get(K id); 8: 9: void Insert(T obj); 10: 11: void Update(K id, T obj); 12: 13: void Delete(K id);

14: } Esta uma interface genrica que contm mtodos bsicos para outras classes. Alguns mtodos so genricos por recebem o tipo da chave primria da entidade (int, long, short). Outra classe da DAL que costumo utilizar uma classe para auxiliar coisas como: criao de parmetros, criao de comandos, entre outras operaes. Como falei anteriormente, voc pode utilizar a Microsoft Data Access Application Block para ajudar com esse tipo de coisas. Essa biblioteca contm inmeros mtodos. Para a aplicao de demonstrao, irei utilizar uma classe bem simples que fiz apenas para esse exemplo. Detalhe que no estou abstraindo o banco da aplicao, mas aconselho voc a criar uma classe que abstraia o banco que esta se utilizando. Outra observao 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 no ser difcil modificar esta classe para ser genrica. Segue o cdigo da mesma: 1: public class DatabaseHelper 2: { 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: this.NomeStringConexao = nomeStringConexao; public DatabaseHelper(string nomeStringConexao) { } public DatabaseHelper() { this.NomeStringConexao = "DefaultStringConexao"; this.MyBdConnection = new SqlConnection(this.NomeStringConexao); #region Construtores #endregion public SqlConnection MyBdConnection { get; set; } public string NomeStringConexao { get; set; } #region Propriedades

22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: } } } } }

this.MyBdConnection = new SqlConnection(this.NomeStringConexao);

#endregion

#region Mtodos Privados

private string GetCorrectParameterName(string parameterName) { if (parameterName[0] != '@') { parameterName = "@" + parameterName; } return parameterName;

#endregion

#region Mtodos Pblicos

public static DatabaseHelper Create() { return new DatabaseHelper();

public static DatabaseHelper Create(string nomeStringConexao) { return new DatabaseHelper(nomeStringConexao);

public void OpenConnection() { if (this.MyBdConnection.State == System.Data.ConnectionState.Closed) { this.MyBdConnection.Open(); }

60: 61: 62: 63: 64: 65: 66: 67: valor); 68: 69: 70: 71: 72: 73:

public void CloseConection() { this.MyBdConnection.Close(); }

public SqlParameter BuildParameter(string nome, object valor, DbType tipo, int size) { SqlParameter parametro = new SqlParameter(this.GetCorrectParameterName(nome),

parametro.DbType = tipo; parametro.Size = size; return parametro; }

public void BuildParameter(string nome, object valor, DbType tipo, int size, List

listParametros) 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: public void ExecuteNonQuery(SqlCommand command) } public void BuildOutPutParameter(string nome, DbType tipo, int size, List listParametros) { SqlParameter parametro = this.BuildOutPutParameter(nome, tipo, size); listParametros.Add(parametro); } public SqlParameter BuildOutPutParameter(string nome, DbType tipo, int size) { SqlParameter parametro = new SqlParameter(); parametro.ParameterName = this.GetCorrectParameterName(nome); parametro.DbType = tipo; parametro.Size = size; parametro.Direction = ParameterDirection.Output; return parametro; } { SqlParameter parametro = this.BuildParameter(nome, valor, tipo, size); listParametros.Add(parametro);

96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133:

{ command.ExecuteNonQuery(); }

public void ExecuteNonQuery(SqlCommand command, bool openConnection) { if (openConnection) { this.OpenConnection(); } this.ExecuteNonQuery(command); if (openConnection) { this.CloseConection(); } }

public void ExecuteNonQuery(string query, params SqlParameter[] parameters) { Exception erro = null; try { this.OpenConnection(); SqlCommand command = this.MyBdConnection.CreateCommand(); command.CommandText = query; command.Parameters.AddRange(parameters); this.ExecuteNonQuery(command); this.CloseConection(); } catch (Exception ex) { erro = ex; } finally { this.CloseConection(); }

134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: } }

if (erro != null) { throw erro; }

public void ExecuteCommands(params SqlCommand[] commands) { Exception erro = null; SqlTransaction trans = null; try { this.MyBdConnection.Open(); trans = this.MyBdConnection.BeginTransaction(); for (int i = 0; i < commands.Length; i++) { commands[i].Transaction = trans; this.ExecuteNonQuery(commands[i]); } trans.Commit(); this.MyBdConnection.Close(); } catch(Exception ex) { trans.Rollback(); erro = ex; } finally { this.MyBdConnection.Close(); }

if (erro != null) { throw erro; }

172: 173: }

#endregion

Para criar essa classe auxiliar deve se considerar muitas coisas: - Abrangncia; -Desempenho; - Utilizao de cach ou no; - Objetos que sero utilizados para manipular os dados (DataDet, DataTable, SqlDataReader); - Se ser utilizado apenas stored procedure ou se todas as querys vo estar na aplicao. - ...; 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 cdigo da nossa classe Pessoa ser o seguinte: 1: public class PessoaDAO : IDataAccessObject 2: { 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: #region IDataAccessObject Members #endregion } public PessoaDAO() { databaseHelper = DatabaseHelper.Create(); #region Construtores #endregion private DatabaseHelper databaseHelper; #region Atributos

20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54:

public Pessoa Get(K id) { Pessoa pessoa = new Pessoa(); SqlDataReader reader = null; try { string query = "SELECT * FROM Pessoa WHERE id = @id"; databaseHelper.OpenConnection(); reader = databaseHelper.ExecuteDataReader(query, new SqlParameter("id", id)); while (reader.Read()) { pessoa.Nome = reader["nome"].ToString(); pessoa.Cpf = reader["cpf"].ToString(); pessoa.DataNascimento = Convert.ToDateTime(reader["dataNascimento"]); pessoa.Email = reader["email"].ToString(); pessoa.ID = Convert.ToInt32(reader["nome"]); } reader.Close(); this.databaseHelper.CloseConection(); } finally { if (reader != null) { reader.Close(); } this.databaseHelper.CloseConection(); } return pessoa; }

public void Insert(Pessoa obj) { string query = "INSERT INTO PESSOA (nome, dataNacimento, cpf, email, sexo)

VALUES (@nome, @dataNascimento, @cpf, @email, @sexo)"; 55: 56: this.databaseHelper.ExecuteNonQuery(query, new SqlParameter("nome", obj.Nome),

57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: } } } }

new SqlParameter("dataNascimento", obj.DataNascimento), new SqlParameter("cpf", obj.Cpf), new SqlParameter("email", obj.Email), new SqlParameter("sexo", obj.Sexo));

public void Update(K id, Pessoa obj) { throw new NotImplementedException();

public void Delete(K id) { throw new NotImplementedException();

#endregion

Simples no ?! A implementao dos mtodos de delete e update, eu deixo como exerccio para voc. :D 2.3. Camada de Negcio A idia da camada de negcio bastante simples. Como falei anteriormente, ela tem o papel de acessar a camada de dados e quem far validaes em cima da mesma. Geralmente, assim como a camada de DAL, costumo criar uma interface nessa camada com os mtodos mais utilizados. Mas para exemplo irei mostrar apenas o mtodo insert da classe de negcio de pessoa. 1: public class PessoaBus 2: { 3: 4: 5: 6: 7: 8: 9: } public PessoaBus() { this.pessoaDAO = new PessoaDAO(); private PessoaDAO pessoaDAO;

10: 11: 12: 13: 14: 15: }

public void Inserir(Pessoa pessoa) { //caso haja validao, ela pode ser feita neste mtodo mesmo. this.pessoaDAO.Insert(pessoa); }

Um padro que costumo utilizar nessa camada so as factories. Que tem o papel de criar instncias das classes. Ou seja, com esse padro eu consigo ter acesso a qualquer objeto acessando apenas uma classe. Exemplo de Factory: 1: public static class FactoryDAO 2: { 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: } } public static ContatoDAO CreatePessoaDAO() { return new PessoaDAO(); } public static ContatoDAO CreateContatoDAO() { return new ContatoDAO();

2.4. Faade Tier Faade simples o bastante que dispensa palavras. Ento vamos ao exemplo: Arquivo 1: Facade.cs 1: public partial class Facade 2: { 3: 4: 5: 6: 7: public Facade() { private PessoaDAO _pessoaDAO; private ContatoDAO _contatoDAO;

8: 9: 10: 11: } }

_contatoDAO = FactoryDAO.CreateContatoDAO(); _pessoaDAO = FactoryDAO.CreatePessoaDAO();

Arquivo 2: PessoaFacade.cs 1: public partial class Facade 2: { 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: } OBS: Notar o uso da palavra chave partial. } public void DeletePessoa(int id) { this._pessoaDAO.Delete(id); } public Pessoa GetPessoa(int id) { return this._pessoaDAO.Get(id); } public void UpdatePessoa(int id, Pessoa pessoa) { this._pessoaDAO.Update(id, pessoa); } public void InserirPessoa(Pessoa pessoa) { this._pessoaDAO.Insert(pessoa);

2.5. UI Tier No irei mostrar exemplo de cdigo desta camada. Pois este o nosso projeto ASP.NET que j conhecemos. A nica coisa que devemos fazer nessa camada encapsular os dados no objeto Pessoa, em algum evento, e em seguida passar o mesmo para algum mtodo da Faade.

Bem, espero ter ajudado a esclarecer como funciona uma aplicao em c# bem arquitetada. O bsico 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 padres de projetos voc estar totalmente apto para criar uma aplicao bem modelada e arquitetada. Em outra oportunidade escreverei um pouco mais sobre arquitetura em camadas e trarei exemplos de sistemas bem arquitetados.

Verso em pdf desse artigo pode ser encontrada no seguinte link: http://cid-2d6d3503299ba131.skydrive.live.com/self.aspx/Artigos/Felipe%20Pimentel%20ArqCamadas.pdf

Leia mais em: Arquitetura em Camadas com C# http://www.devmedia.com.br/arquitetura-em-camadas-com-c/12037#ixzz2dv9C9Efx