.NET - Uma reflexão sobre o Acesso a dados - A era da POO - II


Estamos vivendo na era da orientação a objetos. Linguagens tipicamente procedurais como o COBOL ainda existem mas estão sendo usadas cada vez mais apenas em ambientes restritos como em ambientes de grande porte ou mainframes.

Obs: Esta chegando agora ?  Então veja a primeira parte do artigo aqui : .NET - Uma reflexão sobre o Acesso a dados - A abordagem ADO .NET - I

Atualmente a utilização de classes para organizar dados é a escolha mais natural para a maioria das aplicações. As classes são a base para a programação orientada a objetos. Elas representam os dados facilmente e também realizam ações, publicam eventos, etc.

Do ponto de vista da organização dos dados as classes expressam os dados através de propriedades e métodos.

Usando classes, você pode escolher sua representação interna de dados sem se preocupar sobre como eles serão persistidos, você não precisa saber nada sobre o  mecanismo de armazenamento; Pode ser um banco de dados, um serviço web, um arquivo XML, ou qualquer outra coisa.

A representação dos dados sem ter qualquer conhecimento do mecanismo de armazenamento é referida como a ignorância a persistência e as classes usadas neste cenário são chamados POCOs (Plain Old CLR Objects).

A utilização de classes oferecem benefícios que são importantes para aplicações em particular para aplicações corporativas. Vejamos alguns destes benefícios:

Para exemplificar a teoria vamos montar um cenário onde desejamos exibir os pedidos de um cliente em uma grade (um controle tabular que exibe linhas como um datagridview)

Obs: Como vou usar o banco de dados Northwind.mdf como exemplo vou manter o nome das tabelas e classes no original.

1- O primeiro passo é criar uma classe Order (Pedido)para representar os dados de um pedido.

A classe Order poderá possuir a mesma estrutura da tabela relacionada no banco de dados. Para materializar o exemplo eu vou usar o banco de dados Northwind.mdf do SQL Server e usar a tabela Order e a classe Order gerada por uma ferramenta ORM (no meu caso o Entity framework) conforme exibe a figura abaixo:

Embora possuam a mesma estrutura, percebemos a classe que Order apresenta os tipos de dados existentes na plataforma .NET : string, int32, DateTime, etc., e a tabela Order apresenta os tipos de dados existentes no SQL Server : int , nvarchar(40), datetime, etc.

2- O segundo passo é criar uma classe com um método que lê os dados no banco de dados e os transforma em objetos.

Vamos criar uma classe chamada GetOrders() com o código conforme exibido a seguir:

Public Function GetOrders() As List(Of Order)

Using conn As New SqlConnection(connString)
Using comm As New SqlCommand("select * from orders", conn)
conn.Open()

Using r As SqlDataReader = comm.ExecuteReader()
   Dim orders As New List(Of Order)()
   While rd.Read()
      orders.Add(New Order() With {
       .CustomerCode = DirectCast(rd("CustomerCode"), String),
       .OrderDate = DirectCast(rd("OrderDate"), DateTime),
       .OrderCode = DirectCast(rd("OrderCode"), String),
       .ShippingAddress = DirectCast(rd("ShippingAddress"), String),
       .ShippingCity = DirectCast(rd("ShippingCity"), String),
       .ShippingZipCode = DirectCast(rd("ShippingZipCode"), String),
       .ShippingCountry = DirectCast(rd("ShippingCountry"), String)
      })
    End While
  Return orders
 End Using
End Using
End Using
End Function
public List<Order> GetOrders()
{
using (SqlConnection conn = new SqlConnection(connString))
{
   using (SqlCommand comm = new SqlCommand("select * from orders", conn))
   {
     conn.Open();
     using(SqlDataReader r = comm.ExecuteReader())
     {
        List<Order> orders = new List<Order>();
         while (rd.Read())
         {
           orders.Add(new Order
          {
               CustomerCode = (string)rd["CustomerCode"],
               OrderDate = (DateTime)rd["OrderDate"],
               OrderCode = (string)rd["OrderCode"],
               ShippingAddress = (string)rd["ShippingAddress"],
               ShippingCity = (string)rd["ShippingCity"],
               ShippingZipCode = (string)rd["ShippingZipCode"],
               ShippingCountry = (string)rd["ShippingCountry"]
           }
         );
     }
   return orders;
  }
  }
}
}

Note que embora a tarefa seja bem simples já temos uma boa quantidade de código e a medida que o cenário fica mais complexo essa quantidade tende a aumentar ainda mais.

Para exibir uma ordem em um formulário devemos obtê-la a partir do banco de dados, transformá-la em objetos e exibir suas propriedades usando classes. Essa seria a forma mais simples e o código que expressa isso pode ser escrito assim:

....
shippingAddress.Text = order.ShippingAddress
shippingCity.Text = order.ShippingCity
....
....
shippingAddress.Text = order.ShippingAddress;
shippingCity.Text = order.ShippingCity;
....

Para poder exibir os pedidos e os detalhes dos pedidos relacionados em um grid temos que nos aprofundar um pouco mais visto que neste caso temos a introdução do conceito de modelos de objetos (objetct models).

Mas por quê ???

Porque você não pode representar Order e Order_Detail (pedidos e detalhes dos pedidos) em uma mesma classe; você tem que usar classes separadas da mesma forma que faz com tabelas.

Partindo de uma classe para o modelo de objetos

Já vimos como criar uma classe única e independente e como instanciá-la a partir do banco de dados. Mas o real poder da orientação a objetos vem quando você cria mais classes e começa a vinculá-las entre si.

Em um banco de dados o relacionamento entre um pedido seus detalhes é descrito usando uma chave estrangeira entre a coluna OrderId na tabela Order e a coluna OrderId na tabela Order_Detail. Do ponto de vista do banco de dados esta é a abordagem correta.

No mundo da programação orientada a objetos - POO, você tem que seguir outro caminho.

No mundo POO não há nenhum sentido em criar uma classe Order_Detail atribuindo-lhe uma propriedade OrderId. A melhor solução é tirar proveito de uma característica peculiar das classes: elas podem ter propriedades cujo tipo é uma classe definida pelo usuário; isto significa que a classe Order pode conter uma referência a uma lista de objetos Order_Detail e a classe Order_Detail pode ter uma referência a Order.

Quando você cria esses relacionamentos, você está começando a criar um modelo de objetos.

Um modelo de objetos é um conjunto de classes relacionadas entre si que descrevem os dados consumidos por um aplicativo.

O poder real do modelo de objetos surge quando você precisa mostrar pedidos e os detalhes de pedidos relacionados em uma grade e tem que criar estes relacionamentos.

Ao usar classes, o código da interface é completamente isolado dos problemas relacionados com persistência e obtenção de dados porque já não se preocupa com o banco de dados pois uma parte específica do aplicativo irá buscar dados e  retornar objetos.

Este é o lugar onde o recurso de interface de armazenamento agnóstico usando classes entra em ação.

O modelo de objetos e padrão Domain Model (Modelo de Domínio)

O modelo de objetos não é a mesma coisa que o padrão Domain Model. O modelo de objeto contém apenas os dados, enquanto o modelo de domínio(Domain Model) contém dados e expõe o seu comportamento.

A classe Order é um exemplo de modelo de objeto pois possui propriedades que armazenam dados e nada mais. Para alterar o comportamento da classe Order transformando-a de um modelo de objetos em um modelo de domínio temos que incluir um comportamento à classe Order.

Entender as diferenças entre o mundo orientado a objetos e o mundo relacional é importante, porque elas afetam a maneira de projetar um modelo de objetos ou um modelo de domínio e também o banco de dados.

Podemos entender as incompatibilidades entre os dois mundos abordando cada um em detalhes mais isso seria um assunto muito vasto desta forma estou relacionando abaixo as mais significativas:

Essas incompatibilidades trazem consigo um esforço extra que o desenvolvedor tem que fazer para adequar o seu projeto as melhores práticas e para diminuir este esforço é que surgiram as ferramentas ORM. Sobre esse assunto irei falar mais adiante antes precisamos citar outro fator que aumenta a complexidade do desenvolvimento das aplicações: o desenvolvimento em camadas.

Desenvolvendo em camadas

A chave para desenvolver uma aplicação que seja fácil de manter e de estender é separar as responsabilidades em diferentes camadas lógicas(layers) e às vezes em diferentes camadas físicas (tiers). Você pode ganhar muitos benefícios ao adotar esta técnica.

Quando você adota a separação de responsabilidades em camadas, você cria camadas, e, cada camada tem as suas próprias responsabilidades.

Num modelo clássico em 3 camadas geralmente temos:

- A camada de interface fica responsável pela interface com o usuário
- A camada de negócios mantém as regras de negócio e coordena a comunicação entre a camada de interface e a camada de acesso a dados
- A camada de acesso a dados é responsável pela interação com o banco de dados

Outra grande vantagem em usar diferentes camadas lógicas é a facilidade de manutenção e implantação do aplicativo. Se você precisar alterar a forma como você acessa o banco de dados, você só tem que modificar a camada de dados. Ao implantar os novos pacotes, você implantar somente os assemblies que foram modificados deixando os demais intactos.

Para facilitar o trabalho de desenvolvimento e mitigar a complexidade existente entre a incompatibilidade existente entre o mundo OOP e o mundo relacional, ultimamente têm-se recorrido as recursos de ferramentas ORM.

Aguarde a continuação do artigo em : O advento das ferramentas OR/M.

É isso que iremos abordar na continuação do artigo.

1Pedro 3:10 Pois, quem quer amar a vida, e ver os dias bons, refreie a sua língua do mal, e os seus lábios não falem engano;
1Pedro 3:11
aparte-se do mal, e faça o bem; busque a paz, e siga-a.

Referências:


José Carlos Macoratti