Utilizando System.Reflection e System.Attributes para a construção de uma Ferramenta ORM - Parte 1

• Breve descrição: Construção de uma ferramenta ORM tendo como base os recursos contidos nas Namaspaces System.Reflection e System.Attributes entre outras etc. • Do que se trata: O principal objetivo é demonstrar algumas das funcionalidades

Introdução:

 

Olá a todos os leitores. Este é meu primeiro artigo e gostaria de compartilhar com vocês o fruto de um estudo que fiz há alguns anos atrás da aplicação de algumas técnicas de Reflection, disponíveis no .NET

 

Quando estudo algo relacionado à programação, costumo criar algumas ferramentas para por em prática tudo o que estou estudando. Procuro fazer com que essas ferramentas sejam bastante úteis na medida do possível. Nesse caso a ferramenta ORM que desenvolvi, foi fruto do estudo das técnicas de Reflection e acabei adotando essa ferramenta para alguns projetos que desenvolvi em algumas empresas onde  trabalhei.

 

Gostaria de deixar claro que o principal objetivo aqui é estudar as possibilidades que as Namespaces citadas nos oferecem. A ferramenta ORM criada como exemplo está bastante incompleta, pois se fossemos construir uma ferramenta completa gastaríamos muito tempo e o artigo ficaria enorme, fugindo de seu escopo inicial, além de gastar um tempo enorme para fazer algo que já existe de forma muito mais completa em frameworks como Entity Framework, NHibernate etc. Para este artigo, a ferramenta é apenas uma amostra do que podemos fazer utilizando os recursos que estamos estudando. Temos ciência de sua limitação em face das poderosas ferramentas disponíveis no mercado. Eventualmente, ela poderá ser útil em algum cenário de ambiente de trabalho, porém obviamente essa ferramenta deverá ser incrementada para uma melhor utilização.

 

Como vocês poderão perceber posteriormente, utilizamos diversas namespaces para a construção de nosso projeto, porém vamos nos ater ao foco deste artigo que é estudar as Namespaces System.Reflection e System.Attributes.

 

Para iniciarmos, vamos dar um breve overview sobre as duas namespaces, os conceitos de ORM  e a arquitetura 3 -tier:

 

Reflection:

 

É a técnica para observar, modificar e criar estruturas de códigos executáveis em tempo de execução.

 

A técnica de manipulação e criação de código em tempo de execução existe há muito tempo. Antigamente isso era feito através de manipulação direta de endereços na memória, o que era bastante trabalhoso, muitas vezes gerando bugs indecifráveis e sérias falhas de segurança.

 

Com a evolução das linguagens de programação, mecanismos para representar o código executável foram sendo criados e aperfeiçoados. Esse aperfeiçoamento permitiu que um programa pudesse analisar sua própria representação através de estruturas de alto nível, o que facilitava a construção do código fonte e reduzia o número de possíveis bugs. Esses mecanismos definem o que se conhece por Reflection (ou Reflexão).

 

Existem dois tipos de Reflexão:

 

- reflexão computacional: quando essa representação abrange o código executável;

 

- reflexão estrutural: quando abrange as estruturas definidas no programa (como classes e métodos).

 

O .Net dá suporte à reflexão estrutural através da Namspace System.Reflection e o suporte à reflexão computacional é feito através da Namespace System.Reflection.Emit. Todo esse suporte foi construído para que seja possível analisar a estrutura de um programa, independente da linguagem em que foi escrito, ou seja,  programas escritos em C# podem ser lidos por um programa feito em VB.Net de forma totalmente transparente.


Reflection está presente em muitos pontos da BCL (Base Class Librarys) e em muitas bibliotecas e frameworks do mercado. ORMs como NHibernate e Entity Framework usam reflection para alterar o estado de objetos em tempo de execução. Bibliotecas como Castle.DynamicProxy e a System.Xml.Serialization usam IL Emitting para gerar código em tempo de execução, seja com o objetivo de aumentar a performance, ou até mesmo para estender a funcionalidade de certos objetos.

 

No .NET, o Commom Language Runtime (CLR), gerencia Application Domains. Esse gerenciamento inclui o carregamento de cada Assembly em seu respectivo Application Domain e o controle do layout de memória da hierarquia de tipos dentro de cada assembly. Sendo que Assemblies contém módulos, que por sua vez contém Types e Types contém Members. Reflection fornece objetos que encapsulam o acesso a toda essa hierarquia.

 

Via Reflection, podemos criar instâncias de um Type qualquer em tempo de execução. Podemos até criar Types em tempo de execução para posteriormente criarmos instâncias deste Type.

 

Atttributes:

 

Um atributo é um objeto que representa os dados que você deseja associar a um elemento no seu programa. O elemento que você anexa um atributo é conhecido como alvo [target]. Esses alvos podem ser classes, structs, propriedades, métodos etc.

 

Se você pesquisar no CLR, encontrará inúmeros atributos, Alguns deles são aplicados em assemblies, outros nas classes ou interfaces e alguns, como o [WebMethod], são aplicados nos membros das classes.

Alguns attributos que iremos encontrar estão declarados no AttributeTargets e estão detalhados na tabela abaixo:

 

NOME MEMBRO

USO

All

Aplicado a qualquer um dos seguintes elementos: assembly,classe,contructor,delegate,enum,event,
field,interface,method,module,parameter,property,
return value ou struct

Assembly

Aplicado ao próprio assembly

Class

Aplicado a uma classe

Constructor

Aplicado a um dado construtor

Delegate

Aplicado a um delegado

Enum

Aplicado a uma série

Event

Aplicado a um evento

Field

Aplicado a um campo

Interface

Aplicado a uma interface

Method

Aplicado a um método

Module

Aplicado a um único módulo

Parameter

Aplicado a um parâmetro de um método

Property

Aplicado a uma propriedade (seja get ou set)

ReturnValue

Aplicado a um valor de retorno

Struct

Aplicado a uma struct

 

 

 

Tabela 1 – Attribute Targets

 

 

O atributo mais conhecido em C# é o [Serializable]. Com ele, você garante que sua classe pode ser serializada para o disco ou para a web.

 

Podemos criar nossos próprios atributos e usá-los em qualquer momento. Exemplo:

 


Ilustração 1 - Exemplo de criação de um Atributo

 

 


Ilustração 2- Exemplo de utilização do Atributo criado na Ilustração 1

 

 

Como citado anteriormente, Reflection é uma técnica que possibilita observar e modificar o executável em tempo de execução. Portanto utilizando Reflection, podemos ter acesso a todos os Atributos de uma classe em tempo de execução. E de uma maneira extremamente simples:

 


Ilustração 3 - Exemplo de como ter acesso à atributos customizados em tempo de execução

 

 

No .Net, todos os objetos possuem o método GetType(). Este método nos possibilita trabalhar com algumas fincionalidades de Reflection. Entre elas estão a possibilidade de refletir uma propriedade qualquer de um objeto e listar todos os atributos customizados da mesma, ou então trazer apeans um atributo específico, como o exemplo.

 

Arqutetura em três camadas 3 - tier

 

Muitos projetos de software hoje em dia utilizam o conceito de três camadas (3-tier).

 

Para nos aprofundarmos um pouco sobre esse conceito, vamos definir a diferença entre Tier e Layer:

 

A) Tier 

 

Indica a separação física dos componentes de uma aplicação, o que pode significar conjuntos diferentes como DLL, EXE, etc. no mesmo servidor ou vários servidores, como é descrito na figura abaixo:

 


Ilustração 4 - Arquitetura de três camadas

 

 

Segundo a figura acima, a camada de dados não têm acesso à camada de apresentação e vice-versa, mas há uma camada intermediária – Business Logic Tier, que é a principal responsável pela comunicação entre a camada de dados e a camada de apresentação.
Então, se nós separarmos cada camada pela sua funcionalidade, então chegamos à seguinte conclusão:

 

Presentation Tier (Camada de apresentação) – É a camada onde o usuário final interagirá com a aplicação

 

Business Logic Tier (Camada de negócios) – É a camada que fará a comunicação entre a Data Tier (Camada de Dados) e a Presentation Tier (Camada de apresentação), aplicando as regras de negócios do projeto.

 

Data Tier – Basicamente representa o servidor de banco de dados que gerencia e armazena os dados da aplicação.

 

 

B) Layer

 

Indica a Separação lógica dos componentes.

 

Podemos definir que “Tier” é a separação física de todos os componentes de uma aplicação e “Layer” é a separação lógica de cada componente. Poderíamos separar em vários Layers as Tiers Presentation, Business Logic e Data Tier da seguinte forma:


Ilustração 5 – Representação das Layers

 

 

Para a construção do nosso artigo, vamos nos ater às “Layers” da “Business Tier”.

 

Value Objects ou Model Layer: Esta camada contém classes, geralmente anêmicas (contém somente propriedades e construtores, sem qualquer tipo de inteligência de negócio ou métodos), que refletem tabelas de bancos de dados. Alguns projetos utilizam structs ao invés de classes Um exemplo seria:

 

Tabela 2 – Exemplo de uma classe model

 

Data Access Layer (DAL): Consiste em um projeto que contem classes que farão acesso a dados, ou seja, realizarão as operações de Manipulação, Criação e Recuperação de dados contidos nas tabelas do BD do projeto, por meio de chamadas a Stored Procedures ou execução de código SQL.  Essas classes não devem possuir nenhuma inteligência de negócio e via de regra para cada classe da camada de modelo, é criada uma classe para a camada de acesso a dados. Na maioria das vezes os métodos recebem como parâmetros, objetos da camada Model e alguns deles retornam esses mesmos tipos de objetos. Exemplo:

 


Tabela 3 – Exemplo de uma classe DAL

 

Business Layer: É a camada que utiliza as duas camadas explicadas anteriormente para aplicar as regras de negócio do projeto e fazer a comunicação ente elas. Ela não possui objetos de acesso a dados, e também não possui propriedades para refletir uma tabela no banco de dados. Ao invés disso, ela utiliza a camada Model e DAL, fazendo uso das propriedades encapsuladas na Model e os métodos de acesso à dados da DAL para controlar as regras de negócio.

Imaginemos uma regra hipotética: Para cada produto inserido, um email é disparado para o gerente da área de estoque. Sendo esta uma regra de negócio, ela tem que estar encapsulada dentro da camada de negócio, como demonstra a classe ProdutoBL que contém um método para envio de email, e outro método que faz a inserção do produto, utilizando a classe DAL.

 

Para cada classe da camada model também é criada uma classe na camada Business:

 

 

Tabela 4 – Exemplo de uma classe Business

 

Essa é uma arquitetura básica, haja vista a miríade de novas tecnologias e Design Patterns que estão surgindo a cada momento e que nos dão uma série de novas possibilidades e vantagens. Porém ela é ainda muito utilizada em empresas, pois em alguns ambientes de trabalho, fazer migrações tecnológicas é algo muito burocrático e muitas vezes quase impossível. Mas o fato é que esta arquitetura oferece uma série de vantagens como – maior facilidade em dar manutenção no código e a possibilidade de reaproveitamento de código entre outras.

 

Mas também possui seus inconvenientes. Muito código é escrito, e na maioria das vezes, código repetitivo Um exemplo de código repetitivo são as classes da camada DAL. Em um projeto com este tipo de arquitetura, geralmente as classes DAL possuem métodos quase que idênticos (Inserir, Deletar, Alterar, Recuperar), o que muda são Tabelas/Stored Procedures acessadas, os tipo de parâmetros esperados pelos métodos e os objetos de retorno destes mesmos métodos.

 

Outro inconveniente é que na maioria das vezes em que se utiliza esta arquitetura, os responsáveis pelo projeto não deixam o sistema pronto para eventuais mudanças de Bancos de Dados. Eles utilizam objetos ado.net específicos para cada banco, o que dificulta esse tipo de alteração.

 

Mapeamento Objeto Relacional – ORM

 

Mapeamento objeto-relacional  (ou ORM, do inglês: Object-relational mapping) é uma técnica de desenvolvimento utilizada para reduzir a impedância entre a programação orientada a objetos e o conceito de bancos de dados relacionais. As tabelas do banco de dados são representadas através de classes e os registros de cada tabela são representados como instâncias das classes correspondentes. Em suma ORM faz a conexão entre o “mundo orientado a objeto” e o “mundo relacional”, como demonstra a figura a seguir:


Ilustração 6 – Representação gráfica do conseito ORM

 

Com esta técnica, o programador não precisa se preocupar com os comandos em linguagem SQL; ele irá usar uma interface de programação simples que faz todo o trabalho de persistência.

Não é necessária uma correspondência direta entre as tabelas de dados e as classes do programa. A relação entre as tabelas onde originam os dados e o objeto que os disponibiliza é configurada pelo programador, isolando o código do programa das alterações à organização dos dados nas tabelas do banco de dados.

A forma como este mapeamento é configurado depende da ferramenta que estamos a usar. Como exemplo, o programador que use NHibernate pode usar arquivos XML ou o sistema de anotações que a linguagem providencia. No nosso caso, faremos esse mapeamento utilizando “Attributes”.

Artigos relacionados