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:
É 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.
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, |
|
|
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”.