30 de março de 2011

Sua solução SaaS é multi-tenant ?

A ABC71 está finalizando uma versão SaaS de seu ERP e um parceiro interessado nos perguntou se tínhamos estruturado uma solução multi-tenant já que para ele esta era uma questão importante. No contexto de aplicações publicadas como serviço na internet - o SaaS - "tenant" diz respeito a infraestrutura alocada num servidor e que é destinada a suprir a demanda de cada Cliente da aplicação web.

Assim, uma aplicação multi-tenant é aquela capaz de administrar essa infraestrutura com o menor custo possível, sem que isso afete a performance, a escalabilidade ou a segurança da solução como um todo. Em outras palavras, a aplicação consegue gerenciar a adesão de novos Clientes enquanto garante a segurança dos dados (para que um Cliente não acesse os dados dos outros), a escalabilidade (para que a infraestrutura suporte o aumento de carga inerente a novas adesões) e a responsividade do serviço (para que os acessos dos Clientes ao serviço sejam atendidos num tempo adequado).

Tendo tais considerações em mente, a primeira característica de uma solução SaaS multi-tenant é que ela deve ter um única instalação no servidor, com todos os Clientes compartilhando o mesmo ponto de entrada para a aplicação. Do ponto de visto do fornecedor da aplicação, há uma implicação muito importante para essa decisão: é preciso manter apenas uma instalação, o que reduz custos com espaço (hardware) e simplifica a criação do ambiente inicial para novos Clientes. Do ponto de vista do usuário, a simplificação garante agilidade para que o serviço esteja plenamente disponível logo após a adesão dele ter sido efetivada. Isto é, nenhuma outra providência tem que ser tomada pelo Cliente ou pelo Fornecedor para que todo o ambiente esteja pronto para operação. Esta é uma característica marcante de soluções SaaS.

Isso nos remete à segunda parte da definição: como garantir segurança, escalabilidade e responsividade da solução ? Obviamente, é preciso começar com o planejamento de uma boa infraestrutura de hardware que comporte o movimento esperado para o site. Mas não só. É nescessário ainda ter seu projeto de software preparado adequadamente, mesclando técnicas de programação com uma implementação apropriada do banco de dados. A implementação do banco de dados, aliás, merece uma atenção extra para que se possa equilibrar os requisitos já citados com a facilidade de criação do ambiente para cada novo Cliente.

Uma proposta é manter uma única base de dados que diferencie quais registros pertencem a qual Cliente. Isto pode ser feito através de um código de identificação que seja único por Cliente, o que implica necessariamente que cada tabela do sistema deve guardar essa identificação e que todas as operações devem ser levadas a cabo informando-se esse código. Caso contrário, o sistema teria uma grave falha de segurança, com Clientes enxergando dados uns dos outros. Esse tipo de solução é retratada na figura abaixo:
multi-tenant full

Porém, esse solução embute algumas restrições. Como todos os Clientes compartilham o mesmo banco de dados, fica difícil entregar uma cópia isolada dos dados de um Cliente caso ele solicite. Pela mesma razão, se torna complexo dispor de ferramentas que deem muita liberdade ao Cliente - como um construtor de relatórios ou algo que permita a ele submeter consultas diretamente ao banco.

Um outro aspecto importante pra se preocupar nesta solução é o modo como as consultas (queries) são montadas. Um sistema como um ERP costuma ter operações que envolvem grande massa de dados - cálculo de custos ou execução do MRP, por exemplo. Dependendo de como as consultas são montadas, a tabela pode entrar em lock, comprometendo a experiência dos demais usuários da aplicação Web. Por isso, para esse cenário a melhor opção é a criação de um banco de dados separado para cada novo Cliente.

Com o banco isolado, podemos facilmente enviar ao Cliente um cópia de backup apenas com os dados específicos dele. Ferramentas mais elaboradas também se tornam mais simples de se implementar pois não precisam embutir inteligência extra para bloquear o acesso a dados de outros Clientes que estejam na mesma tabela. Criar um novo banco para cada Cliente não é um processo complexo, podendo ser executado automaticamente com scripts. Por fim, essa é uma solução escalável pois o próprio gerenciador de banco de dados pode ser preparado para fazer o balanceamento de carga, inclusive alocando novos bancos em locais distintos se isso for necessário.

A imagem abaixo ilustra essa nova abordagem:
multi-tenant/multi db

Uma vez que o ponto de entrada é o mesmo pra todos os Clientes, um pequeno banco auxiliar (ou outro mecanismo semelhante) tem que ser implementado para direcionar as requisições ao banco de dados correto. Mas isso praticamente não causa impacto na performance da solução.

Após essa discussão, podemos considerar que o contrário de multi-tenant é algo com um multi-instance, onde cada novo Cliente é associado a um ambiente completo exclusivo para si. Isto é, ele acessa sua própria instalação do sistema, com sua própria versão da aplicação, configurações, pastas e banco de dados. Em um ambiente SaaS, este tipo de solução de infraestrutura pode rapidamente tornar o negócio inviável já que ela implica em custo maior para comportar instalações de todos os Clientes. Além disso, o multi-instance não escala tão bem.

Todas as variáveis apresentadas aqui foram levadas em conta quando a ABC71 planejou sua solução SaaS. Os links abaixo nos forneceram outras informações sobre o conceito de multi-tenant e uma visão mais abrangente sobre estratégias de planejamento do banco de dados :

23 de março de 2011

Problemas comuns ao consumir Web Services para NFe em C#

Ok, já venceu ou está prestes a vencer o prazo para que uma grande parte das empresas utilizem obrigatoriamente a NFe - Nota Fiscal Eletrônica. Mas, como o brasileiro no geral deixa tudo para a última hora, é bastante provável que este post seja útil pra muita gente. Aqui eu abordo alguns problemas comuns que aparecem quando se utiliza os Web Services necessários para implementar no Visual Studio a comunicação com o site da Receita Federal no âmbito da NFe. Estou usando Visual Studio 2010 com a versão 3.5 do .NET mas creio que a maioria das soluções apresentadas sejam aplicáveis também a outras versões.

Neste outro post, eu falei sobre como utilizar Web Services em um programa .NET. Usando as dicas contidas naquele post para adicionar os Web Services da Receita Federal a um projeto do Visual Studio é possível que você se depare com uma mensagem similar a esta:
The request failed with HTTP status 403: Forbidden.
Metadata contains a reference that cannot be resolved: 'endereço-do-web-service'.
The HTTP request was forbidden with client authentication scheme 'Anonymous'.

O servidor da Receita Federal está configurado para exigir a identificação de todos os usuários através de um certificado digital válido. O acesso através do Visual Studio para obter a descrição WSDL dos serviços não é diferente. O que a mensagem está dizendo é que você não informou um certificado que o autorize a acessar o site. No caso do Visual Studio, é feita automaticamente a seleção de um certificado digital que tenha sido inserido na área Personal do Certificate Store do Windows. Por isso, ter instalado tal certificado válido emitido por uma entidade autorizada (Correios, Serasa ou Certisign, por exemplo) é imprescindível já no momento de adicionar os Web Services ao projeto e não apenas para assinar os XMLs que serão enviados à Receita Federal.

Depois que você conseguiu importar os Web Services, você pode usá-los em seu projeto para enviar solicitações de uso de notas fiscais, dentre outros serviços disponíveis. Quando seu programa chama a função de um dos Web Services para enviar ou solicitar informações ao servidor da Receita, este erro é reportado pelo programa:
The HTTP request was forbidden with client authentication scheme 'Anonymous'.

É bastante parecida com a mensagem anterior mas, nesse ponto, você já deve ter instalado um certificado válido no Certificate Store. Então, a causa provável é que você não configurou a forma com que a mensagem SOAP (o XML de comunicação com o Web Service) será transportada via internet, usando o protocolo HTTPS. Quando você importa um Web Service, o Visual Studio automaticamente insere num arquivo chamado app.config toda a configuração associada ao serviço, incluindo aquelas relativas ao transporte das mensagens via HTTPS.

Há particularmente dois parâmetros neste arquivo cujos valores padrões diferem do exigido pelo servidor da Receita: authenticationScheme e requireClientCertificate. O primeiro indica como o usuário será validado, sendo que o valor padrão é "Anonymous" para indicar que nenhuma validação é esperada. Como eu disse antes, o servidor da Receita só libera o acesso se um certificado foi informado. Portanto, o valor desse parâmetro deve ser alterado para "Digest". O segundo parâmetro deve ter valor "true", confirmando que o servidor exige que sua aplicação envie os dados do certificado para garantir a autenticação. O quadro abaixo traz o trecho de um app.config como exemplo, mostrando como fica a configuração para o serviço de consulta de uma nota fiscal:
<binding name="NfeConsulta2Soap12">
...
<httpsTransport authenticationScheme="Digest" requireClientCertificate="true" ... />
</binding>

Por uma questão de clareza, foram omitidas outras configurações que aparecem neste mesmo contexto. Lembre-se ainda que as configurações são feitas por Web Service, isto é, cada serviço da Receita que você for suportar tem que ser configurado neste arquivo da mesma forma que foi feito no exemplo.

Após realizar as modificações do app.config citadas, você ainda pode ser brindado com o erro abaixo:
The client certificate is not provided. Specify a client certificate in ClientCredentials.

Mais uma vez, a culpa é a falta do certificado digital. Nós declaramos que o serviço exige que especifiquemos um certificado. Mas, ao contrário do ambiente do Visual Studio, um Web Service no seu programa não é capaz de detectar automaticamente qual certificado deve ser usado. Por isso, devemos atribuí-lo manualmente antes de fazer a chamada à função do Web Service:
NfeConsulta2Soap12Client lServ;

lServ = new NfeConsulta2Soap12Client ("NfeConsulta2Soap12", _UrlWebservice);
lServ.ClientCredentials.ClientCertificate.Certificate = GetCertificado;

XmlNode status = lServ.nfeConsultaNF2 (ref lCabec, XmlDoc);

O post neste link mostra como navegar no Certificate Store para encontrar um certificado específico. É o que faz a minha função GetCertificado, usada no código de exemplo acima. A variável _UrlWebservice também é minha e contem o endereço (URL) real do Web Service que será acessado.

A arquitetura montada pela Receita Federal permite que você opere num ambiente de homologação (para testes) ou direto no de produção (onde as notas fiscais entram efetivamente na base de dados da Receita). Além disso, cada unidade da Federação disponibiliza seu próprio conjunto de endereços dos Web Services destinados aos contribuintes dessa unidade. É normal, portanto, que os programas para atender a NFe sejam construídos de forma flexível, permitindo receber os diferentes conjuntos possíveis. Obviamente, essa flexibilidade traz consigo o risco de que um endereço errado seja especificado. Se isto ocorrer, a seguinte mensagem será reportada:
Unable to handle request. The action 'http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsulta2/nfeConsultaNF2' was not recognized.

Isso significa apenas que você está tentando chamar uma função que não está implementada no endereço URL que você informou. A solução aqui é simples: verifique o endereço que está sendo passado à instância do Web Service - no exemplo anterior, seria descobrir o conteúdo da variável _UrlWebservice. Você pode até mesmo gerar um log imediatamente antes de fazer a chamada à função do Web Service, se for o caso.

Uma última recomendação. Se for criar um serviço Windows para monitorar as notas fiscais que entram para submetê-las à Receita, fique atento ao fato de que esse serviço pode ser executado com as credenciais de um usuário diferente daquele que está logado no Windows - normalmente, a conta Local System.

Neste cenário, o serviço não enxergará certificados digitais instalados na área Personal do Store de seu usuário Windows e você poderá receber uma porção das mensagens citadas neste post. Por isso, é recomendável que o certificado esteja instalado na área Personal do Store do Local Computer pois nessa área ele estará disponível a qualquer usuário do computador. Não se esqueça de restringir o acesso ao login desse computador por questões de segurança.


18 de março de 2011

Usando cursores personalizados em aplicações Delphi

Sistemas operacionais gráficos como o Windows fornecem nativamente um retorno visual para que o usuário perceba os diferentes tipos de interação que estão disponíveis numa aplicação. Esse retorno visual é conseguido mudando-se a imagem apresentada pelo ponteiro do mouse - o cursor. Com isso, o usuário consegue rapidamente identificar numa tela objetos que podem ser clicados (links) ou que um determinado objeto pode ser redimensionado ou arrastado.

Através da modificação do ponteiro do mouse podemos também notificar o usuário de que algum processamento diferente está ocorrendo. É o caso, por exemplo, do cursor com a ampulheta (indicando que uma operação demorada está em andamento) ou o cursor que indica se uma operação do tipo arrastar-e-soltar pode ou não ser completada. O fato de todas as aplicações do sistema compartilharem o mesmo tipo de relação entre uma funcionalidade e o respectivo formato do cursor facilita a familiarização do usuário, reduzindo a curva de aprendizado dele.

Apesar de o conjunto de cursores distribuídos junto com o sistema operacional ser de grande utilidade, há situações em que um programa pode se beneficiar de ter seu próprio cursor para representar uma situação que seja muito específica dele. Por causa disso, o Windows disponibiliza um conjunto de APIs para que uma aplicação possa criar e manipular cursores do mouse.

Em Delphi, o conjunto nativo de cursores disponíveis fica armazenado numa propriedade do objeto Screen, que mantém informações sobre a tela com a qual a aplicação está trabalhando. A propriedade em questão é a Cursors, um array acessível por constantes numéricas definidas na própria VCL. Para usarmos um cursor personalizado, teremos que adicioná-lo a essa lista e associá-lo a uma constante nossa. A forma mais fácil de fazer isso é carregando um arquivo externo com os dados do cursor, normalmente com extensão CUR (cursor normal) ou ANI (cursor animado):
const crMeuCursor = 1;
begin
Screen.Cursors[crMeuCursor] :=
LoadCursorFromFile('meuCursor.cur');
_Painel.Cursor := crMeuCursor;
end;

O código do quadro acima cria uma nova constante para representar meu cursor (crMeuCursor), usa a função LoadCursorFromFile da API do Windows para carregá-lo a partir do arquivo 'meuCursor.cur'. Depois, ele é atribuido a um painel na tela para que ela passe a exibir o meu cursor sempre que o ponteiro do mouse passar sobre ele. A constante usada pode ser qualquer número inteiro pois os cursores nativos são associados a valores negativos.

O inconveniente com essa abordagem é que o arquivo com o cursor deve ser distribuído junto com a aplicação pois ele deve estar presente para o programa funcionar. Uma alternativa é linkar o cursor como recurso da aplicação, o que embutirá esse cursor no executável final. Para implementar essa alternativa, duas alterações são necessárias. Primeiro crie um arquivo com extensão RC e o adicione ao projeto no Delphi. O conteúdo dele deve ter uma linha semelhante a esta:
meuCursor Cursor "meuCursor.cur"

Isso fará com que o cursor seja linkado com o programa. Agora, a carga tem que ser feita de outra maneira, referenciando o identificador meuCursor atribuído ao recurso:
Screen.Cursors[crMeuCursor] :=
LoadCursor(HInstance, 'meuCursor');

O HInstance usado na chamada de LoadCursor é uma variável global controlada pela VCL que armazena o handle atribuído pelo Windows à nossa aplicação Delphi.

Antes de prosseguir, uma nota sobre como o cursor é desenhado na tela. Um cursor é composto de duas imagens: o desenho do cursor propriamente dito e uma máscara de bits que é aplicada para obter o efeito de transparência quando o cursor é exibido. Isto é, a máscara garante que apenas o desenho real apareça na tela, fazendo com que os demais pontos da imagem estejam transparentes e permitam ver o conteúdo da tela que está por trás. O Windows realiza uma operação bit-a-bit envolvendo ambas as imagens fornecidas e o próprio conteúdo da tela para atingir o efeito citado. Abaixo está um exemplo de como essas duas imagens se parecem.
Cursor

Com isso, o método mais flexível de criação de um novo cursor é trabalhar diretamente com essas imagens bitmap. A flexibilidade está no fato de que você tem acesso total ao contéudo delas, podendo tratá-las por programação antes de usá-las como um cursor. Para trabalhar com as imagens mostradas acima, também podemos adicioná-las a um arquivo RC incluído no projeto Delphi:
penc Bitmap "penc.bmp"
penm Bitmap "penm.bmp"

Vamos precisar recuperar ambos os bitmaps por programação para podermos passá-los ao Windows. O código a seguir retrata como isso pode ser conseguido, levando em conta que os bitmaps estão linkados ao programa através do arquivo RC anterior:
var bmpMask : TBitmap;
bmpColor : TBitmap;
begin
bmpMask := TBitmap.Create;
bmpColor := TBitmap.Create;

bmpMask.LoadFromResourceName(hInstance, 'penm');
bmpColor.LoadFromResourceName(hInstance, 'penc');
{ ... }

Obviamente, os bitmaps podem ser obtidos da maneira mais apropriada para sua aplicação. Quero dizer com isso que você pode até mesmo desenhar em runtime o conteúdo que quiser usando o canvas embutido no TBitmap.

A API do Windows fornece uma função chamada CreateIconIndirect que nos permitirá criar o cursor informando as imagens carregadas. É o que faz o código abaixo:
var iconInfo: TIconInfo;
bmpMask: TBitmap;
bmpColor: TBitmap;
begin
{ ... }
With iconInfo do begin
fIcon := false;
xHotspot := 1;
yHotspot := 64;
hbmMask := bmpMask.Handle;
hbmColor := bmpColor.Handle;
end;

Screen.Cursors[crMyCursor] := CreateIconIndirect(iconInfo);

bmpMask.Free;
bmpColor.Free;
{ ... }

Além de receber os respectivos handles para os bitmaps, a estrutura que é passada como parâmetro para a CreateIconIndirect tem também campos para definirmos o hotspot do cursor, isto é, aquele ponto dentro do cursor usado para determinar o local exato onde um "clique" do mouse aconteceu. Veja também que o resultado da chamada a CreateIconIndirect é lançado diretamente na lista de cursores mantida pelo Delphi, do mesmo modo que as demais formas de se carregar um cursor. Uma diferença fundamental, entretanto, é que nessa forma de se criar o cursor é nossa responsabilidade também destruí-lo quando ele não for mais necessário:
DestroyIcon (Screen.Cursors [crMyCursor]);

Como eu disse, antes de criar o cursor podemos modificar por programação as imagens que o constituirão. Veja um exemplo usando os bitmaps instanciados no quadro mais acima:
{ ... }
for I := 0 to bmpColor.Width - 1 do
for J := 0 to bmpColor.Height - 1 do
if bmpColor.Canvas.Pixels[i,j] = clWhite then
bmpColor.Canvas.Pixels[i,j] := _Seletor1.SelectedColor;
{ ... }

Aqui, todos pontos (pixels) brancos da imagem real são substituídos por uma cor selecionada pelo usuário da aplicação. Isso faz com que o desenho do lápis assuma a cor definida, dando um retorno visual sobre qual cor está selecionada no momento.

9 de março de 2011

Personalizando a aparência da barra de status em Delphi

O componente TStatusBar no Delphi e no C++ Builder é usado para criar barras de status na interface visual dos programas. É apropriado para fornecer ao usuário uma visão rápida sobre a situação de quaisquer aspectos importantes da execução do programa. Por exemplo, para mostrar se um determinado serviço do qual o programa depende está ativo, ou se a tela está em modo de edição, ou ainda data e hora atual, etc.

Apesar de ser útil para grande parte das situações, o comportamento básico desse componente é bem restrito. Você pode criar painéis independentes para exibir cada informação distinta em sua própria área mas você tem que optar por apresentar ou um texto ou uma imagem. No caso de textos, todos os painéis compartilham a configuração de fonte (sua cor, estilos e a família) do próprio StatusBar, impedindo que cada painel possa dar destaque diferenciado à informação que ele contém.

Para contornar essa limitação, a VCL disponibiliza a propriedade Style como membro da classe TStatusPanel, que representa um painel no StatusBar. O valor padrão do Style é psText; se esse valor for ajustado para psOwnerDraw num painel, a responsabilidade por desenhar o conteúdo desse painel passa a ser do programador, através da resposta ao evento OnDrawPanel. Esse evento é disparado pelo StatusBar uma vez para cada painel cujo estilo seja psOwnerDraw.

O trecho abaixo ilustra a interceptação do evento, desenhando manualmente o conteúdo de um único painel. De acordo com um status controlado pelo programa, o texto do painel é exibido numa cor diferente, refletindo o significado do status:
procedure TNfeConfig.StatusBarDrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; var Rect: TRect);
var lCanvas : TCanvas;
begin
{ Verifica o índice do painel pois o desenho pode ser diferente p/ cada um }
if (Panel.Index = 1) then
begin
lCanvas := StatusBar.Canvas;

{ Pinta a cor de fundo com a própria cor do StatusBar }
lCanvas.Brush.Color := StatusBar.Color;
lCanvas.FillRect (Rect);

{ Configura o fonte para exibir a mensagem }
StatusBar.Font.Size := 11;
StatusBar.Font.Style := (fsBold);

{ Varia a cor do fonte com base num status interno meu }
if (_StatusServNFe = ssnOK) then
StatusBar.Font.Color := clTeal
else
StatusBar.Font.Color := clMaroon;

{ Posiciona o texto no painel }
lCanvas.TextOut(Rect.Left + 5, Rect.Top - 2, Panel.Text);
end;
end;

Veja que a primeira coisa que o código de desenho faz é recuperar uma instância do Canvas do próprio StatusBar. Falei sobre o TCanvas em outro post, quando mostrei como customizar a aparência de grids no Delphi.

Como o controle do desenho agora é nosso, devemos preparar o terreno antes, apagando qualquer conteúdo que já tenha sido montado em outra oportunidade. Este efeito é conseguido chamando a função FillRect do canvas, que preenche o retângulo reservado para nosso painel com a mesma cor de fundo configurada no StatusBar. Só pra lembrar: o parâmetro Rect passado para o evento representa a área de um único painel - aquele que está sendo desenhado no momento. É importante respeitá-lo para evitar que sua personalização sobreponha o conteúdo apresentado por outros painéis, atrapalhando a visualização deles.

O exemplo mostrado apenas desenha um texto com uma cor diferente, mas, como temos à disposição o canvas, podemos usar quaisquer recursos de desenho que ele provê, tais como traçar linhas, desenhar um bitmap ou preencher o painel com um padrão qualquer. Consulte a documentação da classe TCanvas para obter maiores detalhes sobre as possibilidades que ela abre na personalização da aparência de seu programa.

Para encerrar, é possível adotar solução similar a esta para customizar a aparência de outros componentes, como Comboboxes e PageControls, bastando configurar o estilo de cada um deles para forçar a execução do evento de pintura associado a eles.

Mais Informações
Classe TStatusBar, Classe TCanvas