quarta-feira, 14 de abril de 2010

Consultas "distinct" com XSL, otimizadas (o modo correto e o não tão correto...)

Esta dica foi enviada pelo meu amigo Luís Fernando Heckler.

As vezes é preciso percorrer os nodos do XML de forma semelhante a cláusula “distinct” do SQL. Por exemplo para gerar uma saída que tenha exatamente uma linha para cada tipo de nodo que estamos buscando no XML original, mas onde esses tipos podem aparecer repetidos.

Essa necessidade surgiu no projeto de um processo BPEL que alimenta um dashboard BAM.

A saída necessária era uma linha para cada departamento, mas no XML de origem os departamentos podiam aparecer mais de uma vez. Para iterar de forma única com XSLT 2.0, podemos utilizar a função xsl:for-each-group

Exemplo: [1]
<xsl:for-each-group select="//tns:departamento" group-by="./tns:dados/tns:nome">

Em [1] temos a expressão XPath que seleciona os nodos que nos interessam e a cláusula que especifica qual deve ser o índice, ou a chave única buscada, no caso, o nome do departamento. Dentro do laço, para identificarmos qual o departamento atual que está sendo processado, podemos usar a função current-grouping-key()

Exemplo: [2]
<xsl:variable name="departamentoAtual" select="current-grouping-key()"/>

Agora entra a dica principal: como fazer subconsultas sobre os nodos filhos de cada departamento, sendo que pode haver mais de uma ocorrência do mesmo departamento?

Primeiro o modo que funciona mas NÃO é otimizado

Digamos que você precise agora buscar todos os nodos “marca” que existem abaixo do departamento atual, que também devem ser buscados de forma única (um registro por marca). O natural é fazer outro for-each-group restringindo o nome do departamento no XPath de seleção das marcas, como abaixo:

Exemplo: [3]
<xsl:for-each-group select="//tns:marca[ancestor::*[local-name(.)='departamento']/tns:dados/tns:nome = $departamentoAtual]" group-by="./tns:dados/tns:nome">

Veja que em [3] estamos filtrando o nome do departamento usando a variável criada em [2], na seleção das marcas. Isso funciona perfeitamente, não está errado. Porém, exige que todo o XML a partir do nível dos departamentos seja parseado novamente, para poder fazer o filtro indicado.

Como melhorar? Detalhes a seguir…

Agora o modo otimizado (1/3 do tempo de processamento nos testes durante o desenvolvimento)

Aproveite as funções e índices em memória que o XSLT lhe proporciona, e não faça parser sobre a mesma parte gigante do arquivo 2x. A função current-group() é um índice em memória que contém o nodeset atual da iteração.

Exemplo: [4]
<xsl:for-each-group select="current-group()//tns:marca" group-by="./tns:dados/tns:nome">

Veja em [4] que estamos solicitando ao processador XSLT que itere as marcas não mais sobre o XML original, mas sobre o nodeset em memória da iteração atual dos departamentos, o que certamente é bem menor pois contém apenas os departamentos do mesmo nome atual e seus filhos.

E para “pegar” a marca atual? Use a função current-grouping-key() que agora está no contexto do laço das marcas, enquanto que o departamento atual nós já salvamos em uma variável lá no exemplo [2].

No desenvolvimento do processo BPEL que alimenta o BAM, uma transformação dessas que levava 3min passou a levar menos de 1min. Parece pouco? Mas são 4 delas por XML e 3 XML processados cada vez que a integração é executada... ;-)

Até a próxima, valeu Luís!

terça-feira, 6 de abril de 2010

[Oracle BPEL] A instância do meu processo sumiu!

Alguma vez você disparou um processo BPEL e não encontrou a instância do processo no BPEL Console?
Isto ocorre quando o processo BPEL lança alguma exceção (não tratada) antes da instância ser persistida no schema de banco da engine (orabpel).

Para resolver este problema podemos forçar a persistência (dehydration) da instância. Para persistir a instância adicionamos um componente "Java Embedding" logo após o inicio do processo BPEL, dentro deste componente utilizamos a função checkpoint(); conforme imagem abaixo:


A utilização desta função pode causar queda no desempenho do processo BPEL durante o tempo de execução devido à sobrecarga adicional para persistir o processo, porém garante que toda a instância iniciada do processo aparecerá no console. 

O BPEL console apresenta o estado da execução somente até o último ponto de persistência, logo alguns erros ainda assim necessitam o acompanhamento dos logs.

Abaixo relato 2 experiências recentes da utilização do checkpoint.

1) Em um processo, que não estava aparecendo no BPEL Console, adicionei a função de checkpoint antes de um "switch" e o processo simplesmente travou, não teve jeito de funcionar, mas foi só passar a função de checkpoint para depois do "switch" que tudo funcionou perfeitamente.

2) Eu desenvolvi um processo que fazia a chamada de um serviço dentro de um loop, algumas instâncias deste processo chamavam até 5 mil vezes este serviço (sim, era necessário). Naturalmente o processo ficou bem instável e volta e meia travava completamente. A solução que encontrei foi colocar o checkpoint dentro do loop, persistindo o processo a cada chamada do serviço, esta solução onerou o processo, porém a necessidade era garantir a invocação do serviço, independentemente do tempo de execução.


Os exemplos deste post foram executados no Oracle BPEL 10g (10.1.3.3)