C# - Uma simples aplicação em Camadas


Quando desenvolvemos um produto de software o objetivo é sempre criar um produto que agregue valor e que tenha alguma utilidade.

Pode parecer óbvio mas essa premissa é desrespeitada com mais freqüência do que imaginamos.

Programar é fácil mas desenvolver software de qualidade é uma arte que requer talento e muito esforço e dedicação.

É por esse motivo que muitos projetos de software fracassam durante o seu desenvolvimento ou após a sua conclusão em não obter os resultados esperados.

Gastar acima do orçamento, produzir um software incompleto ou um software que não soluciona os problemas como deveriam são algumas das consequências do mau gerenciamento de um projeto de software.

Mas porque é tão difícil criar um bom produto de software ?

Para dar uma resposta bem suscinta: Devido a complexidade envolvida no processo de desenvolvimento.

Do sistema mais simples ao mais complexo há somente uma certeza presente no ciclo de desenvolvimento de software: a mudança.

Por isso antes de sentar e começar a programar é preciso pensar e planejar.

Uma das tarefas básicas no processo é definir qual a arquitetura será adotada no processo de desenvolvimento pois adotar uma arquitetura correta pode ajudar no gerenciamento da complexidade e trazer muitos benefícios.

Nota: A arquitetura do software define a estrutura do software, que compreende os componentes com suas propriedades visíveis externamente e os relacionamentos entre eles.(Wikipédia)

Eu não vou me aprofundar no tema arquitetura de software pois o assunto é vasto e meu objetivo neste artigo é outro.

O que pretendo mostrar é que não existe uma receita pronta para se seguir ao se desenvolver um software pois cada software é único em suas características e resultados a serem atingidos e dessa forma não existe uma arquitetura que pode ser aplicada a qualquer projeto de software. Cada caso é um caso particular.

Vamos supor que você tenha que criar um pequeno software que cujo objetivo é manter um cadastro de alunos e que apresente as seguinte funcionalidades:

Esses são os requisitos levantados e embora eles estejam incompletos para não complicar muito o nosso trabalho vamos nos ater a eles.

Como desenvolvedor, analista e programador (o famoso 'programanalista') você tomou as seguintes decisões:

Figura 1.0 - Leiaute do formulário form1.cs

Mas qual arquitetura você vai usar ?

Você poderia simplesmente colocar o código diretamente no formulário, como estamos cansados de ver, e isso resolveria o seu problema.

Mas jamais faça isso, mesmo em uma aplicação simples como esta.

Você pode argumentar que a aplicação não vai ser mantida, nem deverá ser estendida servindo apenas para um trabalho temporário.

Mas, por experiência própria, estou cansado de ver justificativas como essas em softwares que foram criados para 'quebrar um galho' mas que continuam rodando por anos sofrendo 'remendos' e tornando-se verdadeiros 'elefantes brancos'.

A regra é manter a coisa simples mas sempre aplicar as boas práticas de programação; e neste caso usamos a idéia central da arquitetura de software que é a redução da complexidade através da abstração e separação de interesses.

Pensando assim vamos separar as responsabilidades em nossa aplicação e deixar o formulário cuidando apenas da apresentação e da interface com o usuário e colocando o código de acesso a dados em uma classe que terá a responsabilidade de acessar e persistir as informações no banco de dados.

Abaixo vemos um esquema representando a nossa 'arquitetura': (Rigorosamente falando isso não representa uma arquitetura de software)

Isto esta correto ?

Depende.

Para os nossos objetivos, adotando essa arquitetura estamos separando responsabilidades e mantendo as coisas simples. Sob esse aspecto ela esta correto.

Eu poderia usar uma arquitetura em 3 ou 4 camadas e/ou usar um framework ORM como o Entity Framework ou NHibernate ou um framework como o Spring, mas ai estaria adicionando uma complexidade que talvez não fosse necessária pois eu posso obter o mesmo resultado aplicando a separação de interesses criando uma classe e nela definindo as responsabilidades de acesso e persistência dos dados.

Obs:A decisão em adotar ou não essa estratégia envolve a equipe de desenvolvimento mas ser for 'Euquipe' você mesmo é tem que tomar as decisões.

Criando o projeto no Visual C# 2010 Express

Abra o Visual C# 2010 Express e crie um novo projeto do tipo Windows Application com o nome : UsandoCamadas

No formulário form1.cs defina o leiaute conforme mostrei na figura 1.0.

No menu Project clique em Add Class e informe o nome AcessoBD.cs

Neste momento a nossa janela Solution Explorer deverá exibir :

Temos uma solução com um projeto Windows Forms e uma classe AcessoBD.cs

Vejamos a seguir o código da classe AcessoBD.cs onde temos os seguintes métodos:

using System;
using System.Data.SqlClient;
using System.Data;

namespace Macoratti.TresCamadas
{
    class AcessoDB
    {
        private SqlConnection con = null ;
        
        public AcessoDB()
        {
            string path = "c:\\dados\\Cadastro.mdf"; //Application.StartupPath.Remove(Application.StartupPath.Length - 9, 9) + "\\dados\\Cadastro.mdf";
            con = new SqlConnection(@"Data Source=.\SQLEXPRESS;AttachDbFilename='" + path + "';Integrated Security=True;User Instance=True");
        }

        public int Salvar(string[] campos, string[] valores, string sqlIncluir)
        {
            int regAfetados = -1;
            try
            {
                con.Open();
                SqlCommand cmd = new SqlCommand(sqlIncluir, con);
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = sqlIncluir;

                for (int i = 0; i < valores.Length; i++)
                {
                    cmd.Parameters.AddWithValue(campos[i], valores[i]);
                }

                regAfetados = cmd.ExecuteNonQuery();
                return regAfetados;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                con.Close();
            }
        }

        public int Deletar(string[] campos, string[] valores, string sqlDelete)
        {
            int regAfetados = -1;
            try
            {
                con.Open();
                SqlCommand cmd = new SqlCommand(sqlDelete, con);
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = sqlDelete;
                for (int i = 0; i < valores.Length; i++)
                {
                    cmd.Parameters.AddWithValue(campos[i], Convert.ToInt32(valores[i]));
                }
                regAfetados = cmd.ExecuteNonQuery();
                return regAfetados;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                con.Close();
            }
        }

        public  DataTable getRegistro(string sql)
        {

            DataTable dt = new DataTable();
            try
            {
                con.Open();
                SqlCommand cmd = new SqlCommand(sql, con);
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = sql;

                SqlDataAdapter da = new SqlDataAdapter(cmd);

                da.Fill(dt);
                return dt;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                con.Close();
            }
        }
    }
}

Nota: Recomendo colocar a string de conexão no arquivo de configuração da aplicação. Não o fiz aqui por pura preguiça.

Observe que a classe tem uma responsabilidade bem definida : acessar e persistir informações no banco de dados, nada de mensagens ao usuário , nada de lógica de negócio nada de interface.

Vejamos a seguir o código do formulário form1.cs :

using System;
using System.Windows.Forms;

namespace Macoratti.TresCamadas
{
    public partial class Form1 : Form
    {
        AcessoDB accDB = new AcessoDB();
        string sqlSelect = "Select id, Nome, Idade, Email from Alunos";
        int codigo = -1;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (txtNome.Text == string.Empty || txtIdade.Text == string.Empty || txtEmail.Text == string.Empty)
            {
                MessageBox.Show("Informe os valores para nome, idade e email do aluno.");
                return;
            }
            else
            {
                try
                {
                    string sqlInsert = "Insert into Alunos (Nome,Idade,Email) values (@Nome,@Idade,@Email)";
                    string[] campos = { "@Nome", "@Idade", "@Email" };
                    string[] valores = { txtNome.Text, txtIdade.Text, txtEmail.Text };
                    accDB.Salvar(campos, valores, sqlInsert);
                    gdvDados.DataSource = accDB.getRegistro(sqlSelect);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Erro " + ex.Message);
                }
            }
         }

        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                gdvDados.DataSource = accDB.getRegistro(sqlSelect);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Erro ao acessar dados " + ex.Message);
            }
        }

        private void btnExcluir_Click(object sender, EventArgs e)
        {
            if (codigo <  0 )
            {
                MessageBox.Show("Selecione um aluno para excluir.");
                return;
            }
            else
            {
                try
                {
                    string sqlDelete= "Delete from Alunos Where Id = @Id";
                    string[] campos = { "Id" };
                    string[] valores = { codigo.ToString() };
                    accDB.Deletar(campos, valores, sqlDelete);
                    gdvDados.DataSource = accDB.getRegistro(sqlSelect);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Erro " + ex.Message);
                }
            }
        }
        //obtem o código do cliente de uma linha selecionada no datagridview
        private void gdvDados_CellEnter(object sender, DataGridViewCellEventArgs e)
        {
            try
            {
                codigo = Convert.ToInt32(gdvDados.Rows[e.RowIndex].Cells[e.ColumnIndex].Value);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Erro ao acessar o codigo : " + ex.Message);
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
        //permite somente numeros e a tecla backspace
        private void txtIdade_KeyPress(object sender, KeyPressEventArgs e)
        {
            const char Delete = (char)8;
            e.Handled = !Char.IsDigit(e.KeyChar) && e.KeyChar != Delete;
        }
    }
}

No formulário temos o código onde realizamos as operações de cadastrar, excluir e exibir os dados. Observe que no código não fazemos acesso aos dados deixamos isso para a camada de acesso a dados definida na classe AcessoBD.

No formulário realizamos algumas validações mas não se referem a lógica de negócio, que deverá estar em outra classe.

Obs: Pela simplicidade do projeto e para não tornar mais fácil o entendimento eu não criei a camada de negócios onde devem estar as regras e validações do negócio.

Esta tudo certinho ?

Depende.

Poderíamos melhorar o código sob diversos aspectos mas ele cumpre o seu objetivo, é bem simples e estamos , de certa forma, separando responsabilidades. Sob esse aspecto ele esta certinho...

Nota: um dos aspectos a serem melhorados seria a utilização de stored procedures ao invés de SQL como texto (embora tenhamos usados parâmetros), o que dificulta os ataques de injeção.

Então para que complicar ?

Pegue o projeto completo aqui: UsandoCamadas.zip

"Em verdade , em verdade vos digo que vem a hora, e agora é, em que os mortos ouvirão a voz do Filho de Deus, e os que a ouvirem viverão."(João-5:25)

Referências:


José Carlos Macoratti