Mês: março 2012

Integrações entre Sistemas – Parte 1

Introdução

Depois de muito tempo sem escrever, resolvi escrever esse post, motivado por recentes desafios que venho enfrentando. Trata-se de integração de aplicações envolvendo grandes volumes de dados.

Num cenário corporativo, que envolve muitos sistemas em plataformas e tecnologias heterogêneas, a integração torna-se um grande desafio, seja por questões de interoperabilidade de tecnologias, custo de desenvolvimento de interfaces ou mesmo detectar qual a melhor abordagem para se realizar a integração.

No conceito de uma integração simples, online e síncrona, não vejo muitos problemas com as abordagens utilizando web services. Em cenários de compartilhamento de informações em grandes lotes é que as ferramentas são um pouco mais limitadas, e é este ponto que pretendo abordar.

Compartilhamento de informações x Reuso de comportamento

É muito importante diferenciar os dois conceitos. Quando falamos de compartilhamento de informações estamos nos referindo a uma extração de um lote de informações e entregando para outro sistema.

Quando falamos sobre reuso de comportamento, o sistema origem precisa enviar alguns parâmetros ou informações necessárias para que o sistema de destino mande sua resposta. Para determinar a resposta, o sistema destino depende de uma lógica que é de responsabilidade dele. Como sabemos, duplicar lógica sempre traz problemas de manutenção, por isso buscamos sempre um único responsável por cada lógica. Vou ilustrar alguns exemplos:

  • Compartilhamento de informações
    • Enviar todos os clientes do sistema A para o sistema B
    • Enviar todos os pedidos do sistema A para o sistema B
  • Reuso de corportamento
    • O sistema de pedidos “A” precisa utilizar a regra do sistema financeiro “B” para determinar se o limite de crédito do cliente permite que ele faça um novo pedido
    • O sistema de faturamento “A” precisa utilizar a regra de cálculo de ICMS do sistema fiscal “B”.

Existem ainda algumas zonas “cinzas”. No caso apenas de consultar informações de outros sistemas, pode-se encaixar em qualquer dos casos, sempre gerando ineficiências do ponto de vista de desempenho. Exemplo:

  • O sistema “A” precisa de todos os clientes que já compraram mais de R$1 milhão. Pode-se interpretar de 2 casos:
    • O sistema “A” recebe um arquivo com o saldo de todos os clientes (uma extração) e internamente processa o arquivo para obter quais estão acima de R$1 milhão. Desse modo, recebe uma série de informações desnecessárias que serão desprezadas.
    • Outra abordagem é o sistema “A” realizar ma consulta no sistema “B” de forma que este tem a inteligência de realizar o filtro. Neste caso, a consulta pode devolver uma massa enorme de informações e dependendo do meio técnico utilizado, a consulta on-line pode não ser uma boa idéia. A extração também pode não ser uma boa idéia, já que sistemas diferentes usarão critérios diferentes e deveria haver uma extração diferente para cada sistema

Cenários escolhidos para testes

Dentro desse contexto, resolvi realizar alguns testes para simular os prós e contras de alguns métodos de se realizar a integração. O problema idealizado foi:

O sistema “cliente” precisa obter um grande lote de informações do sistema “servidor” (mais de 10.000 registros). Neste cenário, o sistema cliente envia os “identificadores” que precisa das informações do sistema servidor e este sistema servidor devolve o lote de informações.

Foram estudadas as seguintes abordagens:

  1. Web Service, linha a linha: O sistema cliente varre seus identificadores e pergunta, um a um, via Web Service para o sistema servidor e o servidor retorna as informações completas.
  2. Web Service, em lotes de 1000: O sistema cliente varre seus identificadores e realiza perguntas, de 1000 em 1000 identificadores para o sistema servidor.
  3. Extração via arquivo: O sistema cliente gera um arquivo com os identificadores. O sistema servidor recebe este arquivo, processa e gera um segundo arquivo com a resposta. Em seguida, devolve para o sistema cliente que processa o arquivo.

Os testes foram realizados dentro de uma rede local “wireless” entre dois notebooks. Em todos os cenários a máquina cliente, a máquina servidora e a rede foram as mesmas. A idéia de usar uma rede wireless é justamente para que o custo de rede apareça na conta.

As seguintes abordagens não foram estudadas (mais poderão em posts futuros):

  1. .NET Remoting: foi inicialmente descartada por não ser interoperável com outras tecnologias.
  2. WCF com net.tcp: mesma razão do remoting.
  3. Filas: Utilizar sistema de filas (MQSeries ou MSMQ) para trocar os arquivos. É uma abordagem bem interessante que pode ser melhor explorada.
  4. HTTP Assíncrono: Enviar um request HTTP, e em tempo de processamento do request vir entregando a resposta de forma assíncrona. Pode ser mais eficiente que Web Services por não reter todo o conteúdo em memória. Seria quase a mesma coisa que um servidor TCP específico, porém com uma implementação mais simples.

Ainda sobre o detalhe do teste, foi utilizada uma tabela com a seguinte estrutura:

create table ServiceTable (
  ServiceTableID int not null,
  DescServiceTable varchar(200) not null,
  Value float not null,
  CreationDate datetime not null,
  StringField1 varchar(200),
  StringField2 varchar(200),
  constraint PK_ServiceTable primary key (ServiceTableID) 
)

Essa tabela existe na máquina servidora (SQL Server) e foi gerado 2.000.000 registros como massa de teste. Ao perguntar, o cliente envia os “ServiceTableID” e na resposta recebe os demais campos. Estou descrevendo aqui apenas para ter uma idéia do “payload” envolvido.

Em todos os cenários de processamento a máquina cliente recebe as informações e insere numa tabela equivalente.

Cenário 1: Web Service, linha a linha

Neste cenário, a construção da aplicação foi muito rápida. Apenas a exposição de um método no WCF e um consumidor que pede várias vezes a informação.

Já nos primeiros testes, observei que a carga gerada no servidor é enorme de forma que ele começa a recusar conexões no servidorzinho de desenvolvimento do Visual Studio. No IIS roda melhor, mas não dá para montar um cenário desse sem pensar em falhas do servidor, porque as várias idas e vindas no servidor gera um overhead imenso.

Neste cenário, gerando uma pergunta de 20.000 registros a resposta foi em 227,19 segundos ou 3,78 minutos. Por ser extremamente ineficiente, nem gerei as demais consultas com 50.000 ou 500.000 registros.

Cenário 2: Web Service, em lotes de 1000

Neste cenário, a construção da aplicação também foi muito rápida, não existe necessidade de parse das informações. Apenas algumas marteladas no WCF foram necessárias para aumentar o tamanho do request.

O consumidor gera perguntas de 1000 em 1000 identificadores para obter a resposta. Neste cenário, gerando uma pergunta de 20.000 registros, a resposta foi obtida em 26,72 segundos. Usando uma massa de 50.000 registros, 72,10 segundos e com 500.000 registros, 521,67 segundos ou 8,68 minutos. Melhora um pouco, né?

A comparação destes cenários 1 e 2, ajuda a entender o conceito do Fowler de “fine grained” x “coarse grained”, quando pensamos em objetos remotos.

Cenário 3: Troca de arquivo

Neste cenário, a primeira aplicação que construí era apenas o servidor gerando a massa de 20.000 registros e importando no cliente. Este cenário gerou um número muito bom de 10,27 segundos. O único problema é que ele não é realista. Ele não é realista porque o servidor não sabe qual é a requisição que receberá. Num cenário de compartilhamento de informações ele é verdadeiro. Num cenário de reuso de comportamento (em que existe uma requisição/resposta), ele não é verdadeiro.

Em seguida, construí um cenário mais realista. O cliente gera a requisição num arquivo local, copia numa pasta compartilhada no servidor. O servidor processa este arquivo e gera um segundo arquivo local. Em seguida copia ele numa pasta compartilhada no cliente. O cliente recebe este arquivo e o processa.

A primeira coisa chata da construção desse exemplo é ter que gerar toda a lógica de geração e interpretação dos arquivo. Usei XML, o que simplifica muito, mas mesmo assim é um custo maior do que somente importar um WSDL e consumir a classe pronta.

A segunda coisa chata é ficar escrevendo lógica de sincronização do processamento dos arquivos. O FileWatcher ajuda bastante a identificar que um arquivo pingou no file system, porém, ele não elimina a necessidade de ficar tentando abrir o arquivo e tomando exceções até que a escrita no arquivo esteja finalizada. Em resumo, é uma preocupação adicional que não se tem quando se está utilizando transferência somente em streams de rede e sem arquivos.

Um outro ponto observado é que existe um forte acoplamento entre cliente e servidor, de forma que o servidor precisa saber onde devolver o arquivo, ambos precisam ter uma lógica de nomenclatura e formato de arquivos pré-acordada. Caso este servidor precise tratar outros clientes, ele precisará escrever lógica para saber onde entregar o arquivo ou mesmo de ter uma pasta em que vários clientes fiquem “ouvindo” o file system pra saber se seu arquivo chegou.

Neste cenário, para 20.000 registros, 25,34 segundos. Para 50.000 registros, 51,90 segundos. Para 500.000, 439,94 segundos ou 7,33 minutos.

A criação de um formato de arquivo mais otimizado que o XML pode ser uma forma de otimizar o método, porém, o custo de desenvolver uma lógica pra gerar e interpretar esse formato existirá e tornará o desenvolvimento um pouco mais complexo.

Comparativo

Método Desempenho Facilidade de implementação Acoplamento
Cenário 1: Web service linha a linha 20.000 registros: 227,19 segundos Simples Baixo
Cenário 2: Web service em lotes de 1000 20.000 registros: 26,72 segundos
50.000 registros: 72,10 segundos
500.000 registros: 521,67 segundos ou 8,68 minutos
Simples Baixo
Cenário 3: Troca de arquivo 20.000 registros: 25,34 segundos
50.000 registros: 51,90 segundos
500.000 registros: 439,67 segundos ou 7,33 minutos
Difícil em relação a web service Alto

Conclusão

Analisando as 3 abordagens sugeridas, fica claro o trade-off que deve ser realizado: ter um custo de desenvolvimento um pouco maior e um acoplamento maior para ganhar um pouco mais de um minuto para cada meio milhão de registros.

Dependendo das características do processamento (volume e janela), este um minuto pode ser imprescindível, mas não em todos os casos. Cabe analisar o cenário de negócio e ver qual abordagem melhor se encaixa.

Espero em breve fazer uma análise com os outros métodos inicialmente descartados.