28 de outubro de 2009

Design Patterns com Delphi : Facade

A palavra inglesa "Facade" pode ser traduzida como fachada, isto é, a parte de uma construção que é vista por quem está do lado de fora ou a parte da frente dessa construção. Quando aplicada à Engenharia de Software, Facade denomina um Design Pattern estrutural que se propõe a trabalhar como uma fachada no programa que o implementa. Com isso, outras partes do programa enxergarão apenas o que a classe do tipo Facade expõe, sem ter que se preocupar com aspectos ligados à implementação do que é exposto.

O Facade é bastante usado para implementar o código relativo a Casos de Uso num Projeto Orientado a Objetos. Num Caso de Uso, o(s) ator(es) envolvidos interagem com o sistema, acionando operações que estão disponíveis - a "fachada". Para executar a operação solicitada, o Facade cria instâncias de um ou mais objetos de negócio conforme for necessário e transfere para eles a responsabilidade de executar tarefas menores na sequência correta, de forma que no final a operação descrita no Caso de Uso é realizada. O mesmo Facade pode encapsular a funcionalidade de um ou mais Caso de Uso, se isso fizer sentido.
Diagrama UML para Facade

A nomenclatura para as classes envolvidas no padrão Facade é a seguinte :
O Client é a classe (ou trecho de código) que faz uso da interface exposta pelo Facade, tendo conhecimento dos métodos expostos mas nada a respeito de como estão implementados. No diagrama, este participante é representado pela classe TWAtor.
O Facade é a classe principal nesse padrão. É ela quem expõe os métodos que estarão disponíveis para um Client usar. Este papel é exercido pela classe TWOperacaoCompra no diagrama anterior.
São chamadas de Subsystem Classes aquelas classes que são contidas na classe Facade. São instâncias dessas classes quem de fato executa as operações solicitadas, seja na totalidade ou cada uma colaborando com um pedaço da tarefa. As classes TWCliente, TWDocumentoCompra e TWAnaliseCredito são do tipo Subsytem Class no diagrama acima.

No diagrama acima aparece a representação UML das classes envolvidas num Caso de Uso implementado como Facade. Veja que a única relação existente entre a classe Facade e as classes de regras de negócio envolvidas é do tipo Associação. Isto é, não há herança entre elas. A classe Facade apenas contem instâncias de outras classes e as cria conforme vai precisando; para isso, essa classe é obrigada a conhecer de antemão as operações e propriedades das classes relacionadas.

Para representar esse Design Pattern em Delphi, basta declarar as classes de subsistema e incluí-las como membros na classe Facade. Elas serão instanciadas e destruídas conforme a necessidade - possivelmente dentro do próprio método que tenha que executar alguma operação nessa classe.
type
TWCliente = class
{ ... }
_Nome : String;
_Id : Double;

procedure Recuperar;
procedure Gravar;
{ ... }
end;

TWAnaliseCredito = class
{ ... }
function TemCredito (ACliente : TWCliente) : Boolean;
{ ... }
end;

TWOperacaoCompra = class
{ ... }
_Cliente : TWCliente;
_Analise : TWAnaliseCredito;

procedure VerificaCredito;
{ ... }
end;

{ ... }

procedure TWOperacaoCompra.VerificaCredito;
var lCredito : boolean;
begin;
_Cliente := TWCliente.Create;

{ recupera aqui informações do Cliente }

_Analise := TWAnaliseCredito.Create;
lCredito := _Analise.TemCredito (_Cliente);

{ ... }

_Analise.Free;
_Cliente.Free;
end;

Um outro exemplo de uso para o Facade é a criação de interface para operações de um subsistema inteiro. Imagine, por exemplo, que a análise de crédito de um Cliente em seu programa misture regras existentes no seu próprio banco de dados com verificações em um ou mais Sistemas de Proteção de Crédito. Um Facade seria uma boa solução de projeto para encapsular o acesso a todo o subsistema de Análise de Crédito já que esconderia os detalhes de implementação de cada um dos métodos de análise. Além disso, se tais métodos se encontrarem em outras bibliotecas (DLLs ou WebServices, por exemplo), o programa que fará uso do Facade não terá que se preocupar com cargas nem mapeamentos.

Mais Informações
Posts sobre Design Patterns

26 de outubro de 2009

Descobrindo se seu computador comporta o Windows 7

Sempre que sai uma nova versão de um Sistema Operacional - notadamente o Windows - temos uma certa resistência em fazer a troca. As preocupações vão desde o básico "será que o novo sistema terá um boa performance no meu computador ?" até as mais profundas, envolvendo a compatibilidade dos softwares que você mais usa.

Ciente disso, a Microsoft preparou um programa para diminuir os riscos, ao menos no que diz respeito à performance geral do computador e à compatibilidade de periféricos como impressoras e placas de vídeo. O programa chama-se Windows 7 Upgrade Advisor e pode ser executado em computadores com qualquer das versões do Windows que ainda têm suporte, a saber: o próprio Windows 7, o Windows Vista e o Windows XP com Service Pack 2. O programa, que é gratuíto, está disponíel no site da Microsoft através do link http://go.microsoft.com/fwlink/?LinkId=161223. Também pode ser encontrado em outros endereços em sites de download (Info Online, PC World, entre outros).
Tela do Upgrade Advisor

Após o download e a instalação, o programa poderá ser executado para determinar a compatibilidade com o Windows 7. Ele procurará em todo o seu computador por problemas potenciais com hardware, dispositivos existentes e programas que você tenha instalado. Caso encontre algum problema, ele dará recomendações sobre o que deve ser feito antes de tentar partir para a migração para o Windows 7.

Por causa da forma com que o Upgrade Advisor trabalha, você deve ligar os periféricos que você normalmente usa de modo que o programa possa acessá-los e determinar sua compatibilidade com o novo Windows. Isso inclui impressoras, scanners, discos externos, equipamentos que usem a USB (neste caso, o equipamento - como câmeras fotográficas - deve estar conectado na USB e deve estar ligado), etc.

A Microsoft não recomenda que o programa seja executado em ambiente virtualizado (usando o Virtual PC ou Remote Desktop) pois ele poderá deixar de detectar alguns recursos.

Você encontrará no site PC Saudável um tutorial básico para a execução do Advisor.

23 de outubro de 2009

Depurando comandos enviados ao Oracle

Desenvolver um ERP é um trabalho bastante grande já que normalmente engloba vastas áreas da operação de uma Empresa. Se a construção desse ERP tem como premissa ser independente do fabricante do banco de dados onde ele vai armazenar as transações, esse trabalho será ainda maior pois adiciona a dificuldade de compatibilizar a sintaxe dos comandos SQL para realizar as operações no banco de dados.

A ABC71 trabalha com essa premissa e seu ERP é homologado para executar em alguns bancos de dados, entre eles SQL Server e Oracle. Embora haja um SQL padrão, na prática cada fabricante acaba introduzindo diferenças de sintaxe para refletir diferenças de implementação - seja para melhorar performance ou usabilidade, entre outros. É por essa razão que não utilizamos Stored Procedures para implementar funcionalidades do ERP; teríamos praticamente que construir um ERP para cada tipo de banco de dados.

Esse cenário levanta outra questão, relacionada com situações em que é necessária a depuração do programa. Uma vez que os comandos SQL são montados internamente no programa e podem ser diferentes dependendo do banco ao qual são direcionados, como interceptar o comando que é de fato submetido ao banco ? Isto é, será que os valores enviados como parâmetros ao comando SQL estão corretos ? E quanto à sintaxe do comando em si ?

Junto com o SQL Server é distribuído o programa Profiler que é capaz de interagir com o gerenciador de banco de dados e capturar os comandos SQL, incluindo parâmetros, se houverem. No caso do Oracle, não há uma ferramenta que venha junto com o banco para permitir esse tipo de depuração.

Procurando na internet, localizei uma ferramenta chamada Statement Tracer for Oracle, cuja empresa desenvolvedora chama-se Aboves Software. Ela é gratuita mas faz pelo Oracle exatamente o que o Profiler faz pelo SQL Server: captura os comandos SQL submetidos ao banco de dados por uma aplicação. Os comandos capturados são exibidos em linhas, sequencialmente de acordo com a data e hora em que foram submetidos. Ao selecionar uma das linhas, o programa transfere o comando completo para um painel na parte de baixo da interface, permitindo sua análise e, eventualmente, sua cópia.
Tela do Tracer

O Statement Tracer é executado localmente, isto é, no mesmo computador em que você está executando o programa que submeterá as queries. Ele, então, criará uma lista com todos os programas que estejam com alguma conexão ativa com um banco de dados Oracle, exibindo cada programa numa guia diferente. Para iniciar o monitoramento, basta apertar um botão na interface.

O programa permite que você configure quais classes de comando você quer interceptar, incluindo controle de transações, preparação e execução das queries (SELECT, INSERT, etc), visualização de parâmetros em queries parametrizadas e comandos relacionados a operações no esquema do banco de dados (CREATE TABLE, por exemplo). Você pode também incluir ou remover determinadas colunas. Uma dessas colunas traz o tempo que o comando relacionado demorou para executar, sendo uma ferramenta bastante útil para detectar pontos no sistema que possam ter a perfomance melhorada.

Há ainda um configuração de regras para aplicar uma cor diferente às linhas do Tracer, baseado no texto apresentado pelo comando. A utilidade disto é ampla e vai desde descobrir qual comando enviou um determinado valor ao banco de dados até saber quais comandos envolveram determinada tabela ou visão.

Caso queira analisar num outro momento os comandos que foram capturados, basta salvar todo o log para um arquivo texto.

O download da ferramenta pode ser feito neste link. Há outras ferramentas da mesma empresa que podem ser encontradas neste endereço. Veja que há aí um Tracer para interceptar comandos diretamente no ADO, indepentemente do Banco de Dados conectado. No entanto, essa ainda não me pareceu totalmente madura pois às vezes gera invasões de memória.

Mais Informações
Statement Tracer For Oracle

22 de outubro de 2009

Obtendo informações sobre as Threads de um processo

Recebi um feedback baseado no que publiquei no último post (Obtendo informações sobre processos em execução) perguntando se é possível fazer a mesma coisa com Threads. Isto é, dado um handle para um processo é possível obter informações sobre as Threads criadas por ele ?

A resposta é sim, é possível obter uma lista das Threads que estão associadas a um determinado processo. O método para obter esta lista, no entanto, difere um pouco daquele que usei para obter a lista de processos. O problema é que a criação de Threads por um programa é dinâmica e o números de Threads existentes num momento pode não ser mais o mesmo no momento seguinte, dificultando a obtenção de uma lista. (Ok, a lista de processos ativos também pode variar bastante de um momento para outro; volto nesse tópico mais abaixo.)

Para contornar esse problema, a Microsoft disponibilizou na API do Windows uma função chamada CreateToolhelp32Snapshot cujo objetivo é tirar um "instantâneo" do estado de um processo, aí incluindo informações sobre as Threads existentes no momento da chamada da função. No Delphi 2005, ela pode ser encontrada na unit TlHelp32 e possui a seguinte declaração:
function CreateToolhelp32Snapshot(dwFlags, th32ProcessID: DWORD): THandle;

Quais informações sobre o processo serão salvas por esta função depende do valor passado no parâmetro dwFlags . No caso de Threads, há um porém. Especificando a flag TH32CS_SNAPTHREAD, a função ignora o parâmetro com a identificação do processo e captura informações sobre todas as Threads existentes no Windows no momento da execução.
Para percorrer a lista de Threads, há duas outras funções na mesma unit, que são Thread32First (para obter informações sobre a primeira thread) e Thread32Next (para continuar a busca, obtendo as demais). Como estas funções listarão todas as Threads do sistema, é preciso pinçar as informações que interessam. Para isso, use a estrutura THREADENTRY32 (preenchida como retorno em ambas as funções) para comparar a identificação do processo que criou a Thread com a identificação do processo que você deseja. Veja abaixo uma forma comum de usar essas funções:
procedure TForm1.Button1Click(Sender: TObject);
var hSnap, procId : THandle;
cont : integer;
continua : boolean;
threadInfo: TThreadEntry32;
begin
hSnap := CreateToolhelp32Snapshot (TH32CS_SNAPTHREAD, 0);

procId := GetCurrentProcessId();

if hSnap <> INVALID_HANDLE_VALUE then
begin
cont := 0;
threadInfo.dwSize := sizeof (threadInfo);
continua := Thread32First(hSnap, threadInfo);
while continua do
begin
if procId = threadInfo.th32OwnerProcessID then
Inc (cont);
continua := Thread32Next(hSnap, threadInfo);
end;
end;

CloseHandle (hSnap);
end;

Neste exemplo, eu apenas conto quantas threads foram criadas pelo meu próprio programa. Faço isso recuperando o ID do meu processo com GetCurrentProcessId e comparando este ID com aquele contido no campo th32OwnerProcessID da estrutura associada à Thread. Este campo indica qual foi o processo que criou a Thread em questão.

Poderia ainda ter usado a identificação de cada Thread, conforme alimentado no campo th32ThreadID, para obter mais informações ou executar alguma das operações disponíveis na API do Windows para Processos e Threads.

Quando não for mais precisar do snapshot obtido pelo CreateToolhelp32Snapshot, libere os recursos associados a ele através de uma chamada à função CloseHandle.

Usando essa mesma técnica, podemos conseguir a lista dos processos em execução no Windows. Para isso, use as funções Process32First e Process32Next.

20 de outubro de 2009

Obtendo informações sobre processos em execução

Há certas situações quando se constrói um programa onde é preciso interagir com outros programas que também estejam executando no mesmo computador. Embora não seja obrigatório, quase sempre a interação ocorre com executáveis que o próprio programa colocou no ar, tipicamente para observar se o outro programa ainda está em execução e está responsivo ou, no caso de não estar mais em execução, qual foi o código retornado por ele. Podemos, então, usar esse código para determinar se o programa em questão terminou com sucesso ou se encontrou alguma situação que tenha gerado erro.

A Microsoft disponibiliza como parte da API do Windows uma série de funções para se trabalhar com processos neste Sistema Operacional. Vou mostrar como usar algumas delas neste texto.

Há uma função em especial que permite listar todos os processos que estão em execução na máquina. Ela chama-se EnumProcesses e aceita 3 parâmetros, como mostrado na declaração C++ abaixo (extraída do fonte PSAPI.H):
BOOL WINAPI EnumProcesses (DWORD *pProcessIds, DWORD cb, DWORD *pBytesReturned);
Caso você não tenha o arquivo que contem essa declaração, pode mapear manualmente a função a partir da biblioteca PSAPI.DLL, como mostrei no post sobre carga dinâmica de bibliotecas em Delphi.

O primeiro parâmetro dessa função é um array onde são retornados os identificadores dos processos existentes. Note que os valores retornados são identificadores e não Handles para os processos. O parâmetro seguinte é o tamanho em bytes do array que você passou no primeiro parâmetro, enquanto o último parâmetro retorna quantos bytes foram preenchidos no array. Assim, a quantidade de identificadores retornados pode ser calculada divindo o número de bytes retornados pelo tamanho em bytes de cada identificador, representado num DWORD (double word, como mostra a declaração da função).

O trecho de código que segue mostra em C++ o uso típico do EnumProcesses. Em outras linguagens, a sequência de passos será a mesma, bastando respeitar a sintaxe da linguagem.
BOOL ret;
DWORD lProcess[1024], lCount, lNeed, lIndex;
lCount = 1024;
ret = EnumProcesses(lProcess, lCount*sizeof(DWORD), &lNeed);

if (ret)
{
lIndex = 0;
lCount = lNeed / sizeof(DWORD);
while ( lIndex < lCount )
{
/* Use o identificador de cada processo aqui para fazer o tratamento desejado */
lIndex++;
}
}
return ret;

Como não é possível determinar de antemão quantos identificadores serão retornados, recomenda-se criar um array grande. No exemplo, criei o array com capacidade para 1024 identificadores e uso o retorno da função para calcular quantos de fato foram retornados.

A maior parte das funções da API para processos exige um handle para o processo que se quer acessar. Se foi você mesmo quem iniciou o processo através de uma chamada à função CreateProcess, ela já te retorna o handle necessário numa estrutura PROCESS_INFORMATION. No entanto, quando você lista os processos através do EnumProcesses, é preciso converter o identificador de processo que é retornado para um handle associado ao processo. Essa conversão é obtida através de uma chamada a OpenProcess. Usando o array retornado no código acima, teremos a seguinte linha:
HANDLE hProc;
hProc = OpenProcess (PROCESS_QUERY_INFORMATION, false, lProcess[lIndex]);

Nesta chamada, PROCESS_QUERY_INFORMATION indica a permissão desejada para acessar o processo. O valor que passei informa ao Handle do processo minha intenção de usá-lo para obter outras informações sobre esse processo. Outros valores válidos e seus significados podem ser encontrados aqui.

A forma como usei o OpenProcess acima me permite agora obter informações sobre cada processo através da função NtQueryInformationProcess. Dentre as informações possíveis, destaco o nome do executável e os parâmetros que foram usados para chamá-lo, isto é, consigo obter toda a linha de comando para cada processo.

Passando os parâmetros apropriados ao OpenProcess, poderia até mesmo interromper a execução de um processo (através de uma chamada a TerminateProcess) ou descobrir o código retornado pelo processo quando sua execução foi finalizada (chamando GetExitCodeProcess).

19 de outubro de 2009

Gerenciando sua reputação na Internet

A popularização da Internet trouxe um novo tipo de preocupação para pessoas e empresas. Agora, uma vez que uma informação caiu na rede, ficará disponível para praticamente qualquer um acessá-la, elevando a fofoca e o boca-a-boca a um novo patamar, tanto para o bem quanto para o mal. A preocupação passa a ser com o tipo de imagem que aparece a seu respeito quando se faz uma busca na Internet.

O que fazer para evitar que a percepção que se tem de você ou de sua empresa seja negativa na Internet ? Ou se informações indesejáveis aparecem como resultado de uma busca em mecanismos como o Google ou o Bing ?

O gerenciamento de reputação de pessoas e empresas foi tema de um post recente no Blog Oficial do Google. Apesar de serem baseadas puramente no bom senso, as dicas listadas são muito valiosas tanto para quem quer evitar ter uma imagem negativa de si na Internet quanto para quem já está com ela ruim e queira reverter a situação.

A primeira dica listada lá é também a mais óbvia : não publique na Internet informações pessoais suas das quais possa se arrepender mais tarde. A premissa é que mesmo que a informação possa parecer inofensiva no contexto em que você a publicou - uma rede social, por exemplo -, mecanismos de busca podem listá-la fora de contexto e continuará listando-a tempos depois que você a removeu.

E se uma informação que você julga negativa aparecer publicada ? No caso de ter sido você mesmo quem publicou, simplesmente a remova. Isso vale para sites controlados diretamente por você e também para alguns sites de terceiros nos quais é sua a responsabilidade de fazer o upload das informações. Por exemplo, se você colocou uma foto no Orkut ou em sites similares e essa foto aparece de forma pejorativa ou constrangedora em certas buscas online, você pode apagá-la do endereço original. Infelizmente, a busca pode continuar retornando sua foto (ou outra informação) por algum tempo, até que o mecanismo de busca atualize a referência ao site em que ela se encontrava e passe a retornar o novo conteúdo. O Google disponibiliza uma ferramenta para agilizar esse trâmite mas há muitos outros mecanismos de busca na Internet e pode levar um bom tempo até que todos deixem de listar o conteúdo antigo.

Se você não tiver condições de retirar por você mesmo a informação indesejada, veja se é possível entrar em contato com quem gerencia o site e peça a ele que remova. Caso não seja possível entrar em contato ou ele se recuse a retirar, você ainda pode agir proativamente de diversas maneiras para tentar ao menos minimizar o estrago. No caso de Empresas, uma situação típica de recusa para eliminar um texto desfavorável são os sites de reclamações, tais como o Reclame Aqui, onde consumidores que se sintam lesados têm espaço para divulgar suas más experiências.

Veja abaixo uma lista com alguns exemplos de ações proativas que você pode executar:
Crie um perfil público seu ou de sua empresa em ferramentas como o Google Profile ou LinkedIn e inclua nele informações que proporcionem uma imagem positiva. É muito provável que pesquisas feitas diretamente pelo seu nome listem o endereço desse perfil nos primeiros lugares e, ao seguirem esse link, as pessoas depararão com informações positivas antes de qualquer outra.
Se alguém publicou fotos suas das quais você não gosta, escolha algumas que você goste e as publique. Isso ajuda a minimizar o impacto negativo que a foto original possa acarretar.
Se algum Cliente registrou reclamação a respeito de sua empresa, encontre clientes que estejam satisfeitos e peça a eles que também publiquem as experiências deles, realçando a satisfação de serem seus Clientes. Isso oferece um contraponto ao impacto negativo das reclamações.
Se saiu no site de algum jornal ou revista uma notícia desfavorável sobre um assunto que depois se mostrou infundado ou que sofreu uma reviravolta favorável (um processo que sua empresa ganhou, por exemplo) solicite ao veículo que atualize o conteúdo da matéria original, incluindo nela um link para a nova matéria que trata do desdobramento favorável.


16 de outubro de 2009

Design Patterns com Delphi : Decorator - parte 2

Neste post vou dar continuidade ao conceito do Design Pattern Decorator, mostrando como mapeá-lo em classes no Delphi. Vou usar como base o diagrama UML que publiquei no post anterior, isto é, mostrarei a estrutura de código para o exemplo de locadora e como estender dinamicamente a funcionalidade de um item.
Diagrama UML para Decorator

Conforme retratado no diagrama acima, a primeira providência é criar a classe abstrata que servirá de base tanto para itens da locadora como para os Decorators definidos.
type
TWItem = class
{ ... }
_Titulo : String;
_Descricao : String;

procedure Exibir;virtual;abstract;
constructor Create (ATitulo: String;ADescr: String);virtual;
end;

{ ... }

constructor TWItem.Create (ATitulo: String;ADescr: String);
begin;
_Titulo := ATitulo;
_Descricao := ADescr;
{ ... }
end;

As classes TWFilme e TWJogo são especializações simples, bastando codificar heranças do TWItem para tratá-las. Elas representam itens da locadora sem qualquer responsabilidade extra, atuando mais ou menos como um cadastro básico no programa.
type
TWFilme = class(TWItem)
{ ... }
procedure Exibir;override;
end;

TWJogo = class(TWItem)
{ ... }
procedure Exibir;override;
end;

{ ... }

procedure TWFilme.Exibir;
begin
{ ... }
MostraTitulo;
MostraDescr;
MostraOutros;
{ ... }
end;

Já a classe base para representar os Decorators permitidos para itens da locadora tem que armazenar uma referência para o item ao qual estão vinculados. Esta é a diferença fundamental entre um item puro e um item de decoração.
type
TWItemDecorator = class(TWItem)
{ ... }
_Item : TWItem;
procedure Exibir;override;
constructor Create (AItem: TWItem);reintroduce;virtual;
end;

{ ... }

constructor TWItemDecorator.Create (AItem: TWItem);
begin
inherited Create (AItem._Titulo, AItem._Descricao);
_Item := AItem;
{ ... }
end;

procedure TWItemDecorator.Exibir;
begin
_Item.Exibir;
{ ... }
end;
Observe o uso da palavra reservada reintroduce. Ela permite que eu crie uma nova função com o nome de outra já existente na classe-pai dessa herança e, ao mesmo tempo, impede o uso da função original por outras classes do programa. Como neste caso se trata do construtor, na prática estou obrigando o programador a instanciar a classe usando a versão que aceita como parâmetro a referência para um item. Internamente no construtor eu tenho acesso a versão original da função (construtor). Então, uso o item informado no parâmetro da nova função para suprir os valores exigidos pela função original.

O objetivo do Decorator base é garantir que cada novo Decorator criado como herança dele se comporte como um TWItem. Veja pelo corpo da função Exibir acima que este objetivo é atingido usando a referência controlada pelo Decorator. Ao invés de implementar de novo a função, o Decorator simplesmente chama a função equivalente contida na referência do TWItem.

Os Decorators definidos pelo diagrama UML do exemplo, então, são declarados como heranças simples do TWItemDecorator. Cada um deles introduzirá apenas o comportamento adicional a que se propõem executar pois, como foi dito, o Decorator base se responsabilizará por emular o comportamento esperado de um TWItem usando para isso a referência passada para ele.
type
TWItemAluguel = class(TWItemDecorator)
{ ... }
_ValorAluguel : Double;
_ValorMulta : Double;
procedure Alugar;
procedure Devolver;
end;

TWItemVenda = class(TWItemDecorator)
{ ... }
_ValorVenda : Double;
procedure Vender;
end;
E como é que isso deve ser usado no programa ? Nos pontos onde apenas o item em si é necessário, instancie um item com o tipo desejado e faça uso dos seus métodos.
var lItem : TWItem;
begin
lItem := TWFilme.Create ('Blade Runner', 'descrição ...');
lItem.Exibir;
{ ... }
Ao atingir um ponto onde seja necessário se comportar como um item "vendável" ou "alugável", basta instanciar o(s) Decorator(s) informando a referência a um TWItem qualquer, que pode ser um item puro ou um outro Decorator já instanciado anteriormente.
var lItem : TWItem;
lAluga : TWItemAluguel
lVenda : TWItemVenda;
begin
lItem := TWFilme.Create ('Blade Runner', 'descrição ...');
lAluga := TWItemAluguel.Create (lItem);
lAluga.Alugar;
lAluga.Exibir;

{ Passa o decorator de aluguel no parâmetro da referência, sem prejuizo para a funcionalidade do TWItem. }
lVenda := TWItemVenda.Create (lAluga);
lVenda.Exibir;
lVenda.Vender;
{ ... }
Os itens "alugáveis" e os "vendáveis" continuam podem ser usados em lugares onde for esperado o uso de um TWItem.

15 de outubro de 2009

Design Patterns com Delphi : Decorator - parte 1

Normalmente, a modelagem de entidades que compartilhem comportamentos e/ou características em comum gera classes com relacionamentos do tipo "herança" quando transformada em programa. Esse tipo de modelagem tem seu comportamento definido em tempo de compilação, isto é, as instâncias de cada classe conhecem de antemão as operações que podem realizar. Imagine, porém, uma situação em que é preciso adicionar certa funcionalidade a uma única instância de uma classe. Criar uma herança não resolve esse problema pois todas as instâncias da classe herdada compartilhariam a funcionalidade.

O Design Pattern estrutural Decorator aborda esse tipo de necessidade. Ele foi estabelecido para permitir que se adicione novas funcionalidades (ou responsabilidades) a instâncias de uma classe, de modo que ela pode ser estendida em tempo de execução.

Embora o nome Decorator remeta à algo visual, o uso desse pattern não é restrito ao tratamento de interfaces gráficas. Esta é a aplicação mais comum dele (como no exemplo publicado no Wikipedia) mas pode também ser usado para estender regras de negócio. Por exemplo, numa aplicação para uma locadora onde é permitido alugar ou comprar certos itens (filmes, jogos, etc.), certas funcionalidades (exibir o item) e propriedades (título do item) se aplicam a qualquer tipo de item. No entanto, as ações de "alugar" ou "comprar" um item só são válidas em certos contextos da aplicação, como no momento de registrar o aluguel ou a devolução de um filme alugado.

Para implementar esse conceito, é preciso definir uma interface padrão com as ações comuns a todos os itens. Tanto os itens quanto as "decorações" são heranças dessa interface. A diferença é que as "decorações" são construídas para conter a referência a uma instância da interface e é essa instância quem executará as ações básicas para a decoração. Assim, não importa quantas "decorações" estejam adicionadas (empacotadas) umas nas outras pois o resultado final é sempre um objeto capaz de agir tanto como a interface base quanto como uma das decorações adicionadas. Pode parecer meio confuso à primeira vista mas uma olhada no diagrama UML ajuda a clarear.
Diagrama UML para Decorator

Se for necessário acrescentar outras responsabilidades a um item - uma ação para "empréstimo", por exemplo - não é necessário mexer na classe do Item, bastando criar a respectiva decoração e instanciá-la quando for necessário.

A nomenclatura para as classes envolvidas no Decorator é a seguinte :
O Component é quem declara a interface comum a todos os objetos que poderão ter responsabilidades adicionadas dinamicamente, isto é, trata-se de uma classe abstrata que indica o comportamento comum que se deve esperar de todos os integrantes da solução. No diagrama acima, esta interface é representada pela classe TWItem.
É chamado de ConcreteComponent cada um dos itens básicos que implementem a interface estipulada. São tidos como básicos porque não possuem nenhuma das decorações possíveis, embora possam ser atachados a uma ou mais delas. No exemplo, as classes TWFilme e TWJogo são considerados ConcreteComponents.
O Decorator é a classe base para criação de novas decorações (ou responsabilidades). Ela também é uma herança da interface comum (Component) mas com uma diferença : possui internamente um membro que é uma referência a um outro Component e é essa referência quem será responsável por executar as ações para o Decorator e suas heranças. No diagrama acima, o papel de Decorator é exercido pela classe TWItemDecorator.
Cada nova classe ConcreteDecorator permite adicionar uma ou mais ações à classe Component. Atachar uma dessas classes a um ConcreteComponent estende suas funcionalidades já que o ConcreteComponent passa a enxergar as ações disponibilizadas pelo Decorator em questão. É permitido aninhar diversos Decorators, o que na prática adiciona várias funcionalidades ao ConcreteComponent e amplia o escopo onde ele pode ser usado dentro das necessidades do programa modelado. As classes TWItemAlugel e TWItemVenda no diagrama anterior são exemplos de ConcreteDecorator; adicioná-las a um filme ou jogo proporcionará a possibilidade de alugá-los ou vendê-los, respectivamente. Tudo isso em tempo de execução.

Mostro em outro post como mapear essas relações usando Delphi e também como fazer as chamadas para obter o resultado desejado.

13 de outubro de 2009

O que é Computação na Nuvem ?

Um dia desses me perguntaram o que exatamente é a tal de Cloud Computing, ou Computação na Nuvem. Uma imagem que me vem imediatamente à cabeça resume o conceito : Computação na Nuvem é a terceirização do computador. Vejamos o porquê.

Atualmente, a esmagadora maioria dos programas que você usa tem que ser instalada em seu computador. Então, todo o processamento feito pelo programa demanda o uso de recursos quase exclusivamente desse seu computador. Por exemplo, ao abrir uma planilha Excel, é o seu computador que fará os cálculos embutidos nela, consumindo tempo da CPU e memória - além do espaço no disco rígido que ela ocupará quando você salvá-la.

Do ponto de vista do usuário, a ideia central da Computação na Nuvem é que você precisará ter em seu computador apenas o navegador de internet para realizar as suas tarefas. O processamento dessas tarefas e o armazenamento dos resultados será feito "na nuvem", isto é, numa coleção de computadores espalhados na Internet, numa configuração que lembra uma nuvem. Veja o diagrama publicado no Wikipedia:
Computação nas Nuvens

Para o usuário, não importa onde a execução do programa é feita ou onde os dados estão de fato armazenados. Para ele, basta que tudo isso seja acessível de qualquer lugar (o próprio computador, um celular, um PDA, etc). Alguns exemplos disso são os WebMails (Hotmail, GMail, etc) e o Google Docs (onde é possível criar planilhas e documentos texto).

A primeira coisa que chama a atenção nesse arranjo é a que a informação deixa de estar presa a um computador específico. De qualquer lugar e com qualquer equipamento é possível acessar a informação - tanto para o usuário corporativo quanto para o doméstico. Como o grosso do processamento é feito em outro computador, o computador do usuário também não precisa mais ter grande capacidade de memória, de processamento e nem de disco - ele precisará apenas de um navegador (web browser) e de uma conexão com a internet. Um movimento na indústria de computadores aponta para essa tendência : são os netbooks, computadores mais baratos mas com menor capacidade de processamento e acesso à internet, talhados para fazer uso de serviços na nuvem.

Uma decorrência direta desse fato é que o usuário deixa de ter que se preocupar com detalhes de infraestrutura de TI, tais como fazer cópias de segurança dos dados (backup), atualização de softwares antivírus, proteção contra ataque de hackers, etc. Isso tudo passa a ser responsabilidade de quem está fornecendo o serviço na nuvem, normalmente um data center (mesmo que associado a uma outra empresa).

Para uma empresa cujo foco de negócio nada tem a ver com informática, isso significa redução de custos. Conforme aumenta a necessidade de processamento e de armazenamento da Empresa, é o fornecedor do serviço na nuvem quem terá que se preocupar com escalabilidade (garantir um tempo de resposta razoável mesmo com aumento do número de acessos), disponibilidade (ter mecanismos para garantir a continuidade de acesso mesmo quando houver falha em um dos computadores na nuvem). Também é este fornecedor quem terá que resolver problemas de obsolescência, substituindo equipamentos velhos demais. A empresa Cliente desse tipo de serviço demorará mais para ter a necessidade de trocar seus computadores pois eles apenas acessam a internet e, portanto, estarão menos sujeitos à obsolescência.

O termo Computação na Nuvem é relativamente recente mas a solução tecnológica por trás dele já existe há algum tempo e é fortemente calcada em acesso a computadores via internet. Houve uma tentativa anterior de alavancar negócios nesse tipo de modelo e que foi chamado de ASP (Application Service Provider). Na computação na Nuvem, o modelo equivalente é chamado de SaaS (Software as a Service), onde o usuário normalmente paga por transação executada, por tempo de uso do processador, pela quantidade de informações armazenadas ou uma combinação de tudo isso.

Ainda assim, o Google tem um serviço que permite a hospedagem gratuita de softwares Java na nuvem. O serviço chama-se Google App Engine e é gratuito até uma determinada cota de acessos. A Microsoft também possui sua própria plataforma na Nuvem, chamada Azure - mas neste caso, as aplicações tem que ser desenvolvidas usando o framework .NET da Microsoft.

Embora me pareça ser uma tendência irreversível, há ainda alguns pontos a serem resolvidos - principalmente no Brasil - antes da computação na nuvem se tornar totalmente realidade. O primeiro ponto é o da infraestrutura da rede que dá acesso a internet. No Brasil, essa rede ainda é bastante capenga e certos serviços, como transmissão de filmes em alta qualidade, demandam um uso muito intenso de banda. Como a banda não comporta um fluxo tão volumoso, um serviço assim ainda teria qualidade menor do que o aceitável.

O outro ponto, este de cunho cultural, é a resistência que as pessoas têm em deixar seus dados fora de seu controle direto, armazenados num lugar que elas desconhecem. Vai ser preciso doutrinar o usuário a esse respeito, garantindo-lhe (até mesmo por contrato, se for o caso) que seus dados estão mais seguros na nuvem do que permanecendo em um disco rídigo ou DVD, à mercê de um acidente. Na nuvem, ele certamente estará replicado e dificilmente será perdido.

11 de outubro de 2009

Sintaxe do comando INSERT no SQL Server

Por ser parte integrante de uma das 4 operações básicas de uma aplicação que trabalhe com Banco de dados (Incluir, Atualizar, Excluir e Consultar) o comando SQL INSERT é imprescindível neste cenário. Os exemplos que vou mostrar aqui foram montados com o SQL Server da Microsoft mas uma parte deles deve funcionar com outros Gerenciadores de Banco de Dados.

Provavelmente, a sintaxe mais usada desse comando é a que permite a inclusão de um único registro numa tabela. Considere a tabela criada com o comando abaixo:
-- Em SQL Server
create table INFO
(
ID int not null,
VALOR numeric(15,4) null,
DESCRICAO varchar(255) null,
DATA smalldatetime null
)

Para incluir um único registro com valores fixos, a sintaxe básica pede as palavras chaves INSERT INTO seguidas pelo nome da tabela, a palavra VALUES e, entre parênteses, a lista dos valores para cada campo separados por vírgula. Um exemplo com a tabela descrita acima:
INSERT INTO INFO
VALUES (10, NULL, 'Texto da descrição', '20091009')

Os valores para colunas que sejam do tipo texto (Varchar) ou do tipo data devem ser envolvidos em aspas simples. A ordem em que os valores devem ser colocados segue exatamente a mesma ordem com que os colunas foram declaradas na criação da tabela; com essa sintaxe básica, todas as colunas devem ter um valor associado no VALUES. As colunas que foram marcadas na criação da tabela com as palavras NOT NULL são obrigatórias, isto é, não posso passar NULL no lugar do valor reservado para elas.

Para uma tabela com muitas colunas, essa sintaxe é bastante trabalhosa, já que um valor para cada coluna deve ser providenciado, e na ordem correta. Neste caso, há uma variação da sintaxe básica que pode ser útil pois permite que selecionemos quais as colunas que serão alimentadas pelo comando, restringindo a quantidade de informações àquelas colunas que forem realmente necessárias na inclusão. Para isso, basta criar após o nome da tabela uma lista entre parênteses com os nomes das colunas separadas por vírgula. A ordem das colunas nessa lista é irrelevante mas a lista com os valores a inserir deve respeitar a ordem estabelecida.
INSERT INTO INFO
(ID, DESCRICAO)
VALUES
(10, 'Texto da descrição')

As colunas que forem omitidas no comando serão inseridas com valor NULL. Por isso, todas as colunas declaradas com NOT NULL devem ser informadas, caso contrário um erro será reportado e o registro não será incluído. Na tabela usada como exemplo, apenas a coluna ID é obrigatória com essa sintaxe.

E para o caso de querer popular a tabela com valores lidos de outra tabela ? Na verdade, há uma variação da sintaxe do INSERT que permite informar uma cláusula SELECT completa. Então, a origem dos dados pode ser uma única tabela, um JOIN entre tabelas ou mesmo de uma visão (VIEW) que tenha sido criada no banco de dados. Para esse resultado, basta substituir a cláusula VALUES e sua lista de valores pelo SELECT desejado.
INSERT INTO INFO (ID, DESCRICAO, VALOR)
SELECT ID_INFO, 'Texto da descrição', VLR_TRANS
FROM INFOTRANS
WHERE ID_INFO > 100

Mais uma vez, a ordem e o tipo das colunas retornadas no SELECT devem respeitar a sequência de colunas conforme estabelecido no próprio comando, como no exemplo. Caso essa sequencia seja omitida, novamente passa a valer a ordem de declaração das colunas quando a tabela foi criada.

Veja também que o comando SELECT montado trás misturado valores de colunas e valores fixos. Poderiam ter sido usadas também funções do SQL Server ou qualquer outra expressão válida. Em todas essas situações, os tipos de dado das colunas no SELECT devem ser compatíveis com os tipos das colunas que vão ser inseridas. Como resultado do INSERT deste exemplo, podem ser incluidos vários registros de uma vez só - será um registro na tabela INFO para cada registro retornado pelo SELECT.

O SQL Server permite que você informe o nome completo de uma tabela ao montar uma cláusula SELECT, isto é, você pode dizer qual é o nome do banco de dados onde a tabela está e o nome do "Owner" da tabela. Na prática, isso possibilita, por exemplo, a cópia de registros de uma base de testes para a base de Produção.
INSERT INTO INFO (ID, DESCRICAO, VALOR)
SELECT ID, DESCRICAO, VALOR
FROM BASETESTES.dbo.INFO

Se o banco de dados onde o SELECT será submetido estiver em um computador (Servidor de banco de dados) diferente daquele onde você vai executar o comando INSERT, deve ser usada a função OPENDATASOURCE, que existe desde a versão 2000 do SQL Server. Ela é necessária por causa da política de segurança, isto é, você provavelmente vai ter que fornecer um usuário e senha para poder se conectar nesse servidor remoto antes de conseguir extrair dados dele.

8 de outubro de 2009

Comunicação com a Thread principal usando mensagens do Windows

No fonte do programa que eu publiquei no post sobre sincronização de Threads eu usei o recurso de enviar mensagens do Windows para comunicação com a Thread principal, isto é, aquela Thread onde a janela do programa é controlada. Para não misturar as técnicas, não toquei nesse assunto lá mas vou explicar aqui como é que funciona o mecanismo.

O advento da programação visual - onde você simplesmente arrasta botões, caixas de edição e outros componentes para desenhar as janelas de uma aplicação e responder a eventos associdados a eles - escondeu do programador um fato básico a respeito do funcionamento do Windows : tudo em uma janela acontece através do envio de mensagens. Desde a criação e destruição da janela, passando pela pintura e alterações no tamanho até o clique de botões, digitação de textos e mesmo o simples passar do mouse por sobre a janela geram uma infinidade de eventos chamados de "Mensagens".

Cada janela é criada com uma fila interna onde as mensagens recebidas são colocadas pelo Windows para que a janela possa recuperá-las e tratá-las. Por isso, nos primórdios da programação para Windows, todo programa era montado em cima de um laço para obter cada mensagem e uma estrutura do tipo switch/case para tratar cada uma delas - muitas tratadas automaticamente pelo próprio Windows, como mover ou redimensionar a janela. Hoje, esse mecanismo continua existindo mas é encapsulado por classes dentro de frameworks como a VCL ou o .NET.

Uma mensagem é constituida de um código que a define (ex: WM_PAINT quando a janela precisa ser desenhada ou WM_MOUSEMOVE quando passamos o mouse sobre uma janela), o handle que indica a qual janela a mensagem é direcionada e parâmetros com informações extras para complementar a descrição do evento. O significado dos parâmetros varia de acordo com a mensagem.

Além das mensagens definidas pelo próprio sistema operacional, é permitido às aplicações registrar tipos de mensagens privadas, cujo siginificado é restrito ao escopo da própria aplicação. Foi isso que fiz no programa de exemplo das Threads, declarando duas novas mensagens:
var
WM_NOTIFY_THREAD_STATUS : Cardinal;
WM_NOTIFY_THREAD_TERMINATED : Cardinal;

{ ... }

WM_NOTIFY_THREAD_STATUS := RegisterWindowMessage ('WM_NOTIFY_THREAD_STATUS');
WM_NOTIFY_THREAD_TERMINATED := RegisterWindowMessage ('WM_NOTIFY_THREAD_TERMINATED');

O Windows atribuirá ao nome passado para a função RegisterWindowMessage um código interno de modo que novas chamadas a ela sempre retornarão o mesmo valor. Não é o caso do exemplo mas eu poderia ter usado esse mecanismo para comunicar com outro programa meu em execução no mesmo computador e que registrasse uma mensagem com o mesmo nome.

Com a mensagem registrada, minha Thread pode notificar o seu status à janela principal enviando a ela uma mensagem. Cada Thread de classificação no exemplo é associada a uma estrutura chamada _Status que, entre outras coisas, possui o handle da janela do programa, usado no envio da mensagem.
PostMessage (_Status.OwnerForm.Handle,
WM_NOTIFY_THREAD_STATUS,
Integer (_Status), 0);

A função PostMessage da API do Windows coloca uma mensagem na fila de mensagens da janela indicada pelo Handle mas não aguarda seu tratamento. Os demais parâmetros são o código da mensagem e informações adicionais específicas dessa mensagem - no caso, o ponteiro para toda a estrutura de status da Thread.

No caso do Delphi e do C++ Builder, o tratamento de mensagens direcionadas a uma janela pode ser estendido criando uma sobreposição da função WndProc, que existe em todas as janelas.
{ Definição na classe da Janela }
protected
procedure WndProc(var Message: TMessage);override;

{ ... }

{ Corpo da função }
procedure TWSyncThread.WndProc(var Message: TMessage);
var lMsg : TWMsgNotifyStatus;
begin
if Message.Msg = WM_NOTIFY_THREAD_STATUS then
begin
lMsg := TWMsgNotifyStatus(Message);
lblMsg.Caption := lMsg.Status.Texto;

{ ... }

lMsg.Result := -1;
end else
inherited;
end;

Veja o uso da palavra-chave inherited. Isto repassa o tratamento de todas as outras mensagens à mesma função existente na classe pai (a própria classe TForm). Sem isso, a janela deixa de funcionar.

A estrutura TWMsgNotifyStatus é definida por mim para conter os parâmetros esperados por mensagens do tipo WM_NOTIFY_THREAD_STATUS. A estrutura é construída de forma que os parâmetros passados à função PostMessage se encaixem nela, facilitando o acesso e a interpretação do significado de cada parâmetro.

7 de outubro de 2009

Trabalhando com Threads em Delphi - Sincronização com Eventos - parte 2

Para mostrar um exemplo prático dos conceitos de que falei na primeira parte da sincronização de threads com eventos, vou trabalhar com o seguinte cenário : um programa que tenha 4 listas distintas com uma quantidade aleatório de items a serem ordenados. Cada lista será montada e ordenada em uma thread separada, simulando que as listas vieram de fontes diferentes. Como elas têm uma quantidade diferente de itens, haverá uma thread especial, desenhada apenas para aguardar o términio das demais. O efeito disso é que as threads estarão sincronizadas e o programa poderá prosseguir com sua execução nesse ponto com as 4 listas ordenadas. Veja novamente o esquema geral para esse cenário.
Threads com eventos

Para implementar essa solução em Delphi, eu comecei criando uma unit que centralizará o controle da sincronização. Ela terá uma variável global para representar o evento "Término das threads de Classificação" e funções para incrementar e decrementar a quantidade de Threads ativas. O objetivo é que cada nova thread de classificação que for criada incremente um contador interno e, quando essa Thread terminar, o contador seja decrementado. Quando ele atingir o valor "zero", o evento será acionado para avisar que as listas já estão ordenadas:
var _QtThreads : Integer;
_CS_QtThreads : TCriticalSection;

procedure IncQtThreads;
begin
{ Incrementa o contador global de threads. Usa a seção crítica para proteção já que várias threads podem tentar o acesso simultaneamente }
_CS_QtThreads.Acquire;

try // Por segurança, trata exceções
Inc(_QtThreads);
finally
_CS_QtThreads.Release;
end;
end;

procedure DecQtThreads;
begin
{ Decrementa o contador global de threads. Usa a seção crítica para proteção já que várias threads podem tentar o acesso simultaneamente }
_CS_QtThreads.Acquire;

try
Dec(_QtThreads);

{ Se Chegou a zero, significa que as threads de classificação já terminaram a execução. Então, notifica a ocorrência do evento p/ quem estiver aguardando }
if _QtThreads = 0 then
evtThreadsSort.SetEvent;
finally
_CS_QtThreads.Release;
end;
end;

A linha evtThreadsSort.SetEvent notifica que o evento esperado ocorreu. Então, na classe que representa a Thread aguardando, uso a variável do evento para esperar pela notificação:
procedure TWThrEsperaSort.Execute;
var res : TWaitResult;
begin
{ Espera no máximo 1 minuto e meio pelo evento }
res := evtThreadsSort.WaitFor(90000);
{ Se tudo correu bem, neste ponto as listas já estão ordenadas. }

Este código suspende a execução da Thread enquanto aguarda. É por essa razão que eu não uso a Thread principal para aguardar; se fizesse isso, o usuário perderia a interação com o Form durante a espera, dando a impressão que o programa todo "travou". Essa Thread só continuará sua execução quando o evento esperado ocorrer ou se o tempo de espera (timeout) expirar ou ainda se houver algum erro inesperado.

Se tudo correu bem, as listas estarão classificadas no ponto do código imediatamente após a chamada ao WaitFor e o processo que depende da classificação de todas as listas pode continuar.

Eu criei uma classe de Thread para realizar a classificação de listas e, no construtor dela, faço o incremento do contador de threads ativas. O decremento desse valor é chamado na função de finalização.
Constructor TWThrSort.Create (PStatus: TWThreadStatus);
begin
inherited Create (true);
IncQtThreads;
FreeOnTerminate := true;
OnTerminate := OnThreadFinish;
{ ... }
end;

procedure TWThrSort.OnThreadFinish(Sender: TObject);
begin
{ ... }
DecQtThreads;
{ ... }

Duas coisas a observar no trecho acima. Quando chamo o construtor herdado Create, informo True no parâmetro createSuspended, o que cria a Thread em estado suspenso. Isto significa que terei que iniciá-la manualmente. O outro ponto é que ajustei a propriedade FreeOnTerminate para True, indicando que a finalização ocorrerá automaticamente quando encerrar a função Execute.

A ligação disso tudo se dá no ponto em que as Threads de classificação são instanciadas. Neste caso, inclui o código abaixo como resposta ao clique de um botão no meu Form:
{ Reseta o evento para garantir que está no estado inicial }
evtThreadsSort.ResetEvent;

{ Cria a thread de espera passando o handle desse Form }
TWThrEsperaSort.Create(Handle);

{ Cria as threads de classificação }
for i := 0 to 3 do begin
lStatus := TWThreadStatus.Create;
{ ... }
thds[i] := TWThrSort.Create(lStatus);
end;
{ sincroniza o início das threads de classificação }
for i := 0 to 3 do
thds[i].Resume;
{ ... }

É preciso colocar a variável do Evento em seu estado inicial (ResetEvent) antes de criar a thread de espera e só então podemos criar as demais threads, dedicadas à classificação das listas.

Mas, porque iniciar as Threads manualmente neste caso? Se eu deixar a criação da Thread iniciá-la automaticamente e a primeira Thread for rápida o suficiente, ela poderia terminar antes da segunda Thread ter tempo de incrementar o contador, o que, por sua vez, acionaria o Evento antes da hora pois o contador das threads ativas voltaria ao valor zero. Ao fazer a criação e a execução em pontos distintos, o contador de threads ativas estará em seu valor máximo quando as threads se iniciarem. Assim, mesmo que a primeira Thread execute muito rápido, o contador só voltará ao valor zero quando todas terminarem, acionando o Evento no momento apropriado.

As classes TEvent e TCriticalSection encapsulam chamadas a funções da API do Windows para trabalhar com sincronização. Essas funções podem ser chamadas diretamente, se for necessário. A documentação para elas pode ser encontrada no site MSDN.

Para fazer o download código fonte do exemplo em Delphi 2005, clique aqui.

5 de outubro de 2009

Usando um Web Service num projeto do Visual Studio

O envio das Notas Fiscais Eletrônicas para a Receita Federal é feito através de Web Services disponibilizados pela própria Receita. Quando falei neste blog a respeito da assinatura digital do XML, não tinha entrado neste assunto. Agora, me perguntaram como é que isso pode ser feito.

Os Web Services são uma solução de chamada remota de funções (mais ou menos como o CORBA ou o RPC) baseada no protocolo SOAP definido pelo Consórcio para padrões na Internet, o W3C. O SOAP foi amplamente adotado por ser baseado em XML, ou seja, a estrutura das mensagens trocadas poder ser estendida com facilidade.

Um Web Service é descrito por um arquivo de extensão WSDL (Web Service Description Language), que é na verdade um XML com uma estrutura bem conhecida. É o WSDL quem estabelece quais são as operações disponíveis no Web Service, isto é, quais são as funções (ou Serviços) que estão disponíveis, quais são os nomes e os tipos dos parâmetros que devem ser passados para a função. O WSDL permite até mesmo a criação de tipos de dados complexos, baseados em definições XSD.

Cada chamada a uma operação de um Web Service é feita construindo um XML que inclui a identificação do computador remoto (em forma de um endereço Web, também chamado de URL). Esse XML também inclui o próprio nome da função que será chamada no computador remoto e a lista dos parâmetros exigidos para chamar tal função, com seus nomes, seus respectivos tipos de dados e os valores com os quais desejamos que a chamada seja feita. Da mesma maneira, o retorno produzido pela chamada remota à função é também um XML, o qual contem as informações devolvidas pelo computador remoto ou uma mensagem de erro, no caso de a comunicação não ter sido possível.

A boa notícia é que o Visual Studio .NET (e outros IDEs, como o Delphi) consegue tratar todo esse trâmite para você.

Em qualquer linguagem da plataforma .NET, você pode adicionar ao seu projeto uma referência ao WebService, do mesmo jeito que você faz quando adiciona um ActiveX (COM). Na versão que eu tenho do Visual Studio (Team System 2008), esta opção está no menu Project e chama-se Add Service Reference mas em algumas versões pode estar como Add Web Reference. Ao selecionar esta opção, o Visual Studio vai abrir um diálogo para você informar a URL onde o web service está publicado. No caso da Nota Eletrônica, há um Web Service para cada tipo de operação - enviar a Nota, verificar o status da Nota Enviada, Cancelar uma Nota, contingência, etc.

Ao dar OK nesse diálogo, o Visual Studio lerá o WSDL contido na URL e gerará algumas classes para você poder fazer as chamadas remotas. A maioria das classes geradas é usada internamente apenas para tratar o protocolo de envio e recebimento do Web Service - aí incluindo as tags para o protocolo SOAP.

Do ponto de vista de quem está programando, basta criar uma instância da classe principal para a operação (por exemplo, NfeRecepcao) e fazer chamadas às funções internas dela. A impressão é que funções locais estão sendo chamadas mas, na realidade, a plataforma .NET cuidará de moldar o nome da função e os valores de seus parâmetros no formato XML correto, acionará o protocolo IP para a chamada remota e aguardará até que o computador remoto execute a função. O retorno produzido, que também é um XML, será desempacotado automaticamente e o valor da função calculada remotamente será devolvido pela função local de modo que o seu programa não sabe que a execução foi feita em outro computador.

2 de outubro de 2009

Trabalhando com Threads em Delphi - Sincronização com Eventos - parte 1

Um dos aspectos mais interessantes a respeito do uso de threads (ou linhas de execução) em um programa é a possibilidade que se abre para divisão real de tarefas dentro do programa. Se, para resolver um problema computacional, é possível dividir uma tarefa grande em passos menores que podem ser executados simultaneamente, então a resolução desse problema pode ser implementada com threads. Num computador com múltiplos núcleos (CPUs), essa solução significará uma execução mais rápida, com um tempo de resposta menor já que a execução das threads nesse ambiente será de fato simultânea.

Uma situação real em que isso pode ser aplicado é quando você tem massas de dados heterogênas (vindas de fontes diferentes) e que precisam ser ordenadas antes de serem utilizadas como se viessem uma única fonte.

Logo de cara, podemos identificar um grande problema : eu não sei de antemão o tamanho dessas fontes de dados, isto é, quantos registros poderão vir de cada fonte. Só é possível prosseguir com o processamento após todas as threads terminarem a sua parte na busca e ordenação. Assim, certamente teremos que sincronizar essas threads para que o uso da lista final ordenada só ocorra depois do fim da última thread de ordenação.

O mecanismo que os sistemas operacionais disponibilizam para esse fim é chamado Evento. No Delphi, a classe TEvent implementa esse mecanismo, encapsulando chamadas à API do Windows. Um objeto do tipo Evento é um objeto de sincronização cujo estado pode ser explicitamente sinalizado, isto é, o programador pode indicar manualmente que ocorreu um determinado evento que outras threads estejam aguardando.

Em termos de programação, é preciso criar um objeto global do tipo Evento e dar-lhe um nome conhecido que o diferencie de outros Eventos que possam existir. O objeto tem que ser global - e não parte de uma thread particular - porque ele terá que ser acessível por outras threads do programa. Ao dar início a cada uma das threads que queremos aguardar, o programa incrementa um contador. Uma thread em especial, então, ficará em estado suspenso, aguardando a notificação de ocorrência do evento. Conforme cada thread vai sendo finalizada, o contador é decrementado. Na última delas, o contador chegará a zero e a thread pode emitir a notificação de que todas já completaram seus respectivos processamentos e o fluxo de execução da tarefa pode continuar. Veja a representação desse funcionamento no gráfico abaixo:
Threads com eventos

A thread que vai aguardar a sinalização pode ser a principal do seu programa. Entretanto, isso não é recomendável porque é a thread principal quem recebe as mensagens do Windows, incluindo aquelas resultantes de interação com o usuário. Portanto, ao ter sua execução suspensa, o programa perderá interatividade e dará ao usuário a impressão de que está "travado".

É claro que neste tipo de cenário devemos dobrar a atenção no tratamento de erros e exceções no processamento das threads, caso contrário a sinalização do evento pode nunca ocorrer e o programa aguardará indefinidamente.

Volto num próximo post com código em Delphi para implementar o exemplo.