Data Digester

Um problema recorrente de diversos tipos de sistemas é a necessidade de leitura e processamento de arquivos em diversos formatos (CSV, posicionais, XML, JSON ou outros), normalmente para importação e processamento de dados. Entre essas fontes poderiam ser citados:

  • Dados fornecidos de fontes externas
  • Dados obtidos de sistemas legados
  • Scripts
  • Serviços

Desta forma, o módulo data-digester do Jmine existe exatamente para padronizar e facilitar a solução deste problema.

Estrutura

A importação de arquivos pelo data-digester é realizada em duas ou três etapas conforme o esquema abaixo:

../../_images/Data-digester.png

Esta divisão de responsabilidades facilita tanto o reaproveitamento dos drivers para um determinado formato de arquivo ao longo da aplicação (caso um mesmo tipo de arquivo seja usado em mais de um ponto da aplicação) quanto a adaptação da aplicação em caso de mudança no formato de algum arquivo a ser lido, bastando para isto alterar o Driver a ser utilizado na leitura. Uma descrição mais detalhada das etapas componentes do processo de importação encontra-se abaixo:

Driver

Trata-se do componente que fará a leitura propriamente dita do arquivo. Recebe um DigesterFile como parâmetro, e cria um DigesterBean, que é apenas um de um VO para o conteúdo do arquivo lido em formato padronizado e que será utilizado pelos passos seguintes. Como um DigesterFile é apenas um objeto contendo o nome do arquivo importado e um InputStream correspondente ao arquivo, o Driver consegue ler o arquivo de forma transparente à maneira como a importação foi feita no sistema (via interface web, serviço, script ou outros).

Processor

Responsável pelo processamento do DigesterBean gerado, recebendo um bean e agindo de acordo. Embora se possa fazer com que o Processor insira os dados diretamente no BD, para fins de reutilização o mais comum é que o mesmo gere serviços que serão executados por um ServicePageExecutor para manipulação e inserção dos dados em Banco. Este comportamento é recomendado pois reduz as responsabilidades do Processor, que não necessita mais conhecer as entidades do sistema que serão manipuladas a partir do processamento do arquivo.

FileRegistry

Fileregistry é uma entidade que é referenciada por uma String fileCode única e representa um par Driver/Processor que deve ser usado na importação de um dado tipo de arquivo. Assim, por exemplo, para a importação de serviços, há no JMine dois FileRegistries, com um mesmo Processor, mas com Drivers específicos para importar arquivos XML e Planilhas respectivamente.

ServicesPageExecutor

Recebe os Serviços gerados pelo Processor e os executa, realizando as alterações no banco indicadas pelas informações do arquivo lido. Como não é o propósito deste artigo descrever o funcionamento da infra-estrutura de serviços do JMine, é possível saber mais sobre seu funcionamento no artigo :doc: Serviços <../services/index>

Controle Transacional

A princípio, é coerente que o processo de importação de um arquivo ocorra dentro de uma única transação, sendo feito o rollback caso quaisquer dados do arquivo não possam ser lidos e processados. Porém, devido à quantidade de dados e serviços gerados pode ser interessante executar a importação em várias transações, obtendo-se um ganho significativo de performance dependendo do tamanho do arquivo. Neste sentido, o Processor pode ficar responsável por gerenciar o uso de múltiplas transações, passando como parâmetro ao executor de serviços a forma como este controle deve ser feito.

Utilizando o módulo

Para utilizar o módulo data-digester para leitura de arquivo não é necessário utilizar explicitamente todo o processo descrito acima. O módulo data-digester possui uma fachada, da classe DataDigesterController, que pode ser recuperada via Spring. Esta fachada processa um arquivo a partir do DigesterFile e um FileRegistry, entidade que como já descrito corresponde ao par Driver/Processor que se deseja utilizar para processar um o arquivo importado. Assim, para se utilizar desta fachada, devem ser realizados os passos:

  • Implementar ou determinar o Driver e o Processor a serem utilizados para processamento do arquivo;
  • Importar o arquivo jmine-tec-data-digester.xml para importar os beans e serviços necessários;
<import resource="jmine-tec-data-digester.xml" />
  • Adicionar um FileRegistry correspondente a este par Driver/Processor (o serviço FileRegistryService permite fazer isso de forma simples);

Implementando novos drivers/processors

O Jmine fornece alguns drivers de exemplo para tratar:

  • arquivos posicionais
  • arquivos CSV,
  • Script Beanshell e Velocity
  • XMLs de Serviços
  • Planilhas (XLS) de Serviços.
  • Pacotes de Importação

Para outros casos, porém, pode ser necessário implementar novos drivers/processors bem como tipos de DigesterBean. Esta seção fornece um overview das implementações necessárias para fazê-lo.

Driver

Uma implementação de Driver deve implementar a interface DigesterDriver<T extends DigesterBean> . Esta interface define apenas um método:

T parse(DigesterFile file, Map<String, String> driverProperties);

em que DigesterFile corresponde ao arquivo lido, como já dito. driverProperties é um mapa mantido por questões de compatibilidade para casos em que dados relevantes do arquivo não façam parte de seu conteúdo, como por exemplo um arquivo cuja data de referência faça parte apenas de seu nome.

O JMine fornece alguns Drivers abstratos para facilitar a implementação de Drivers para tipos mais comuns de arquivo, a saber:

  • AbstractLayoutDriver: Para arquivos texto baseados em layout (e.g. CSV)
  • AbstractScriptDriver: Para arquivos de script. Por exemplo os drivers para script BSH e Velocity do JMine extendem desta classe.

DigesterBean

Deve-se criar uma classe implementando DigesterBean que irá representar os dados contidos no arquivo lido na forma desejada. Esta classe deve implementar três métodos:

boolean isComplete();

isComplete() indica se o arquivo representado pelo DigesterBean foi sido importado e lido por completo e sem erros.

int getNumberOfItems();

getNumberOfItems() deve fornecer o número de itens a serem processados/executados pelo Processor que receber este Bean.

Date getFileDate();

getFileDate() deve fornecer a data efetiva a que se refere o arquivo, se aplicável.

Processor

Deve-se criar uma classe que extenda a interface Processor, implementando o seginte método:

void process(DigesterBean bean);

Este será o método chamado para fazer o processamento propriamente dito do DigesterBean correspondente ao arquivo lido. O JMine fornece alguns Processors abstratos para facilitar a implementação de alguns casos comuns de Processors, a saber:

  • AbstractProcessor: Inclui algumas facilidades, como um ExecutionLogController injetado, para facilitar os logs do processamento de arquivos.
  • AbstractServiceProcessor: Classe abstrata com facilidades para Processors que executem serviços.

Pacotes de Importação

Pacotes de Importação são arquivos comprimidos (formato ZIP) que podem conter vários arquivos a serem lidos e processados pelo Data Digester. Tanto o Driver quanto o Processor para os Pacotes de Importação já se encontram disponíveis nativamente no JMine. Para utilizar-se desta funcionalidade, basta criar o arquivo ZIP contendo os arquivos que se deseja importar e um arquivo importar_arquivos.txt, localizado na raiz do ZIP, com a lista de arquivos a serem importados (com o caminho completo a partir da raiz do ZIP) e os respectivos filecodes a serem utilizados no formato:

arquivo|fileCode para o arquivo

Por exemplo, para um arquivo hipotético de uma aplicação que controle a coleção de filmes de seus usuários, poderíamos ter um pacote de importação como o da figura:

../../_images/Pacote_dados.png

Assim, o arquivo importar_arquivos.txt poderia conter:

users/user_account_data.xml|IMPORT_USER_XML
movies/user_collections.json|COLLECTION_JSON_IMPORTER
movies/imdb_data.csv|IMPORT_IMDB_CSV

em que IMPORT_USER_XML, COLLECTION_JSON_IMPORTER e IMPORT_IMDB_CSV seriam fileCodes para as entradas de FileRegistry responsáveis por processar os dados de cada arquivo.

ExecutionLog

Um dos princípios básicos no JMine é que toda ação no sistema deve ser rastreável. Neste sentido, cada Importação e processamento de arquivo também é rastreada por meio da criação de um ExecutionLog com os dados da importação. Este ExecutionLog contém o arquivo importado na íntegra, mais dados relevantes à execução do mesmo, como:

  • Origem do arquivo (se foi importado por um usuário via tela ou devido a um processo interno do sistema, por exemplo)
  • Data de Geração do Arquivo
  • Nome do Arquivo
  • Lista de ExecutionLogItem relacionando os serviços gerados pelo processamento do arquivo.

Com isto é possível ter todas as informações a respeito de uma dada execução de arquivo de forma detalhada. Ainda, a partir da lista de ExecutionLogItem, é possível ao usuário obter apenas os serviços em cuja importação houve erros, exportá-los e re-importá-los em outro formato (e.g. planilha) após realizar as correções necessárias.

ExecutionLogController

De forma semelhante ao DataDigesterController, existe uma fachada para a funcionalidade de ExecutionLog denominada ExecutionLogController, que pode ser obtida via Spring e permite iniciar o Log dos processamentos e verificar se está ocorrendo o Log. Normalmente não será necessário se preocupar com isto, pois na importação e processamento de arquivos utilizando o DataDigesterController, a criação do Log é feita de forma automática.