Artigo .net magazine 69 - AOP e Design Patterns na prática

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (0)  (0)

Criando um componente para cache facilmente plugável

Atenção: esse artigo tem um vídeo complementar. Clique e assista!

[lead]Do que trata o artigo

Este artigo aborda a melhora de performance de aplicações, através da utilização de cache. É apresentado um componente para cache que utiliza conceitos de AOP (Aspect Oriented Programming), sendo facilmente plugável a qualquer sistema já existente ou novo, com baixo nível de acoplamento. Além de AOP, também veremos no decorrer do artigo conceitos de orientação a objetos e os Design Patterns Strategy, Template Method e Simple Factory.

Para que serve

O cache serve para guardar dados temporariamente em memória, evitando acessos recorrentes ao seu meio de armazenamento original (banco de dados, disco ou outro recurso), de forma que a obtenção da informação seja mais rápida. O componente para cache apresentado serve para centralizar e padronizar a forma que os nossos sistemas lidam com este assunto.

Em que situação o tema é útil

Sistemas em geral (Web, Web Services, Remoting, Windows Services, WCF, Desktop, etc.) que fazem acessos frequentes à base de dados ou outro recurso para recuperação de informações, podem se beneficiar da utilização do cache para melhora de performance e redução da taxa de transferência de dados de rede.

Resumo do DevMan

Neste artigo veremos como melhorar a performance de aplicações através da utilização de cache. Para tal, vamos contextualizar o conceito de cache e sua aplicação em sistemas de informação. Em seguida definiremos os requisitos de um componente para centralizar e padronizar o mecanismo de cache e explicaremos o conceito de programação orientada a aspectos. Com os requisitos em mãos, partiremos para a estruturação da arquitetura e finalmente para o desenvolvimento e teste do componente.[/lead]

Podemos encontrar diversos artigos a respeito de melhora de performance de aplicações. Em grande parte deles nota-se um consenso quanto à utilização de um recurso, o cache. Como exemplo, temos o artigo de Rob Howard: 10 Tips for Writting High-Performance Web Applications, disponível no site MSDN (veja a sessão links), que aponta a utilização de cache de formas diferentes, direta ou indiretamente em quatro das dez dicas apresentadas.

Mas afinal o que é cache e por que ele é importante para a melhora de performance? Em uma definição geral, o cache é um meio de acesso rápido, onde podem ser guardadas informações temporariamente para posterior consulta, como alternativa a acessá-las diretamente em seu meio de armazenamento padrão – que normalmente apresenta tempo de acesso à informação mais demorado do que o do cache. Na prática, utilizamos o cache em nossos sistemas para guardar em memória as informações que precisamos acessar com frequência, evitando a busca recorrente destas informações em outros recursos que podem apresentar tempo de resposta mais demorado, como por exemplo, um arquivo em disco, um sistema gerenciador de banco de dados disponível em outro servidor ou outro dispositivo disponível na rede ou internet.

Um dos cenários em que o cache torna-se um fator importante para a melhora de performance é o de sistemas que consultam dados com frequência em recursos distribuídos, como é o caso das aplicações que utilizam SGBDs (sistemas gerenciadores de bancos de dados) localizados fisicamente em servidor separado ao da aplicação. A utilização do cache em um sistema deste tipo ajuda a reduzir a taxa de transferência de dados de rede, favorecendo de maneira geral todos os sistemas que utilizam a mesma rede. Ajuda também a melhorar o tempo de processamento e resposta do sistema, uma vez que alguns dados poderão estar disponíveis em memória local, evitando sua busca através da rede e posterior processamento no SGBD, o que por sua vez também colabora com a diminuição da carga no SGBD, beneficiando todas as aplicações que o utilizam.

A implementação de um mecanismo de cache simples poderia se dar através de uma estrutura que permita guardar dados em memória, acessíveis através de um valor chave. Por exemplo, se desejarmos guardar em cache uma lista de clientes, poderíamos ter o código identificador do cliente como chave de acesso ao item do cache e todos os demais dados como o seu conteúdo. Desde as suas primeiras versões, o .NET Framework possui recursos que nos permitem implementar facilmente um mecanismo de cache, dentre os diversos tipos de projetos que podemos construir: Web Applications, Web Services, .NET Remoting, Windows Services, WCF, Windows Applications, etc. Podemos citar como exemplo, a criação de um mecanismo através da utilização de variáveis do tipo Dictionary (System.Collections.Generic.Dictionary) , que nos permite manter uma lista de qualquer tipo de dado, possibilitando acesso através de uma chave única. Podemos também utilizar o objeto Cache (System.Web.Caching.Cache) do ASP .NET (caso estejamos desenvolvendo aplicações Web), disponível no .NET Framework justamente para este fim.

Porém, quando formos construir uma aplicação pensando na utilização de cache, podemos nos deparar com diversas questões, como por exemplo: Em qual camada do meu sistema devo tratar o cache? Como posso fazer para que meu código que trata de regras de negócios, persistência de dados ou mesmo da apresentação, não fique repleto de códigos que tratam especificamente do cache de dados e suas particularidades, tornando-se altamente acoplado ao mecanismo de cache que escolhemos? Como implementar um mecanismo de cache facilmente plugável aos sistemas já existentes? Como deixar este mecanismo configurável, de forma que seja possível, dentre outras configurações, ativá-lo e desativá-lo facilmente?

[nota]Nota do DevMan

Acoplamento é um conceito de design de software que se refere ao quanto as classes ou subsistemas estão interconectados ou dependem uns dos outros. Dizer que uma classe possui alto acoplamento com um componente externo significa dizer que a classe conhece detalhes do componente e em boa parte do seu código há refêrencias para ele. Devemos projetar componentes pensando no menor nível de acoplamento possível, tornando o código mais modularizado, o que o torna menos complexo e favorece evoluções futuras.[/nota]

Veremos a seguir, como criar um componente para tratar o cache em nossas aplicações, de forma a endereçar todas estas questões.

[subtitulo]Identificando os requisitos do componente[/subtitulo]

Vamos começar o processo de estruturação do componente pela definição dos principais requisitos que queremos que ele atenda:

  • RQ01 - O componente deverá ser facilmente plugável a sistemas já existentes: Queremos construir um componente que possa ser facilmente plugado tanto em sistemas novos quanto em sistemas existentes. Desta forma, poderemos criar novos sistemas performáticos e também poderemos melhorar a performance dos sistemas legados através da inclusão do componente criado;
  • RQ02 - O componente deverá apresentar baixo nível de acoplamento com os sistemas em que for utilizado: Não é desejável que para implementar o cache em um sistema, seja necessário adicionar várias linhas de código em suas classes, de forma que o sistema fique altamente acoplado ao mecanismo de cache;
  • RQ03 - O mecanismo de cache deverá ser transparente para o sistema em que for implementado: Desejamos que o componente não interfira no objetivo chave da classe onde ele for implementado. Ou seja, se o componente for referenciado na camada de persistência da aplicação, é desejável que a sua implementação não tenha que ser intercalada com os demais comandos que recuperam e tratam as informações do banco de dados;
  • RQ04 - O componente deverá ser estendível: O componente deverá ser construído de forma que o repositório onde as informações serão guardadas temporariamente possa ser facilmente alterado, permitindo assim que diferentes repositórios possam ser utilizados para diferentes cenários de utilização do componente. A princípio, o componente deverá suportar utilização nos ambientes Web e Windows, sendo necessária a escolha dos repositórios apropriados para cada um dos ambientes;
  • RQ05 - O componente deverá ser configurável: Uma vez plugado em um sistema, o componente deve permitir parametrização através do arquivo de configurações da aplicação (app.config para aplicações Windows e web.config para aplicações Web);
  • RQ06 – O componente deverá permitir as seguintes configurações: Quantidade de milissegundos, após a inclusão do dado no cache, para que seja considerado como expirado; Intervalo de tempo em milissegundos em que o componente buscará e removerá itens expirados no cache; Ativação ou desativação da funcionalidade de cache;
  • RQ07 – O componente deverá permitir armazenamento de qualquer tipo de dado em cache: Tipos primitivos, demais tipos disponíveis no .Net Framework, bem como tipos criados pelo desenvolvedor deverão ser suportados. Uma vez armazenados, os dados deverão ser acessíveis através de uma chave de acesso;
  • RQ08 – O componente deverá efetuar limpezas periódicas no repositório de cache: Até mesmo os dados pouco voláteis (com baixa frequência de mudança) podem sofrer alterações. Para possibilitar que estas alterações sejam refletidas nas aplicações que utilizam o mecanismo de cache, os dados devem ser renovados periodicamente no repositório do cache. O componente deverá então, ter um mecanismo de limpeza periódica, possibilitando que dados modificados sejam atualizados também no cache e que dados utilizados com pouca frequência não ocupem espaço no cache desnecessariamente.

Definidos os requisitos, vamos tratar nos tópicos seguintes da solução técnica para endereçá-los.

[subtitulo]Endereçando os requisitos[/subtitulo]

Para criarmos um componente facilmente plugável, que apresente baixo nível de acoplamento com o sistema que o utilize e cuja utilização seja transparente (RQ01, RQ02 e RQ03), vamos utilizar recursos de programação orientada a aspectos conhecidos como AOP (aspect oriented programming).

Simplificadamente a AOP refere-se à injeção de aspectos (trechos de código / comportamentos comuns) a um código existente, de forma que seu comportamento seja alterado, porém, seu código original fique intacto. Como exemplo, imagine um método que recupere informações de um banco de dados. Podemos, através da AOP, incluir funcionalidades de log, tratamento de exceções, cache de dados, tracing e outros, sem alterarmos uma linha sequer do código já existente.

[nota]Nota: Veremos nos tópicos seguintes como utilizar a AOP através da utilização do PostSharp, um framework que facilita a utilização da AOP com .Net. Tanto a AOP quanto o framework PostSharp são temas extremamente abrangentes e por isto não serão aprofundados neste artigo. Caso você tenha interesse em saber mais sobre o assunto, a 59ª edição da .net magazine traz o artigo AOP – Utilizando Orientação a Aspectos em .NET, o qual é um bom ponto de partida. Adicionalmente, neste artigo estão listados alguns links sobre os principais sites sobre o assunto (veja a sessão links).[/nota]

A Listagem 1 apresenta um exemplo de código onde é empregado o conceito de AOP através do framework PostSharp. Note que a forma de injetar a funcionalidade é realizada através da inclusão de uma classe atributo no método. Quando o código é compilado o PostSharp faz a inclusão do código correspondente ao atributo, no método em que ele foi empregado. Desta forma podemos acrescentar funcionalidades aos nossos métodos de forma transparente, simples e com baixo nível de acoplamento.

"

A exibição deste artigo foi interrompida :(
Este post está disponível para assinantes MVP

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?