Busca no Blog

segunda-feira, 14 de maio de 2018

Auditoria com o CDI do Java

 


Aviso: Antes de iniciar a leitura, saiba que este texto não é um tutorial, já que ele não ensina a fazer do zero uma auditoria com o CDI do Java. Na verdade, ele passa ideias e procedimentos usados para implementar a funcionalidade de auditoria feita na empresa em que trabalho. Ou seja, ele pode ser útil na implementação de uma auditoria, mas está longe de dar uma solução passo-a-passo.

O problema

Sou uma apaixonada por dados, gosto de analisar, e responder perguntas através deles. Assim um dos impedimentos que enfrentei nas empresas em que já trabalhei é a falta de informações para responder as nossas perguntas.

Não deveria, mas é uma prática comum, implementar aplicações grandes que fazem alterações importantes e perigosas sem guardar dados simples como quem fez aquela mudança, quando foi feita, o que foi alterado. Muitas vezes por causa da pressa, ou por não dar a devida importância, isso é deixado de lado.

Só percebemos o problema em situações como quando algum usuário diz que não fez determinada alteração, e pede para informar quem a fez, mas não temos como responder, já que o dado crucial não foi gravado. Assim comprovar situações, identificar fraudes, usuários desonestos, até mesmo algum funcionário trapaceiro, um erro, ou responder uma pergunta importante para a inteligência do negócio acaba não sendo possível por causa de uma falha lá no início do projeto.

É aquele caso em que uma simples auditoria das alterações poderia ter evitado todos esses problemas, mas a boa notícia é que o erro pode ser consertado, pois mesmo não tendo sido implementada desde o início, a auditoria pode ser feita depois para responder as perguntas que continuarão a vir no futuro.


A auditoria

Auditoria pode parecer um processo complicado, que não é tão importante, que gasta muito tempo para implementar, que sobrecarrega o sistema, que tem um custo alto, que é melhor fazer utilizando frameworks do mercado que pode não dar todas as informações que precisamos ou informações demais, enchendo tabelas e mais tabelas com dados que podem ser inúteis, meio que tornando quase que uma busca de uma agulha num palheiro

Mas engana-se quem pensa assim. Auditoria é um desafio interessantíssimo, e podemos fazê-la de maneira que se encaixe ao nosso negócio ou demanda. A escolha por um framework ou algo customizado vai depender da necessidade de cada projeto. E implementá-la, mesmo que de maneira customizada, é relativamente simples. Tudo porque existe o paradigma de programação orientada a aspectos, que as vezes, pode parecer até mágica.


O desafio

O meu desafio foi fazer a auditoria em um sistema antigo, robusto, com tabelas cheias de dados, mas que não guardava quase nenhum log.

A demanda tinha as seguintes características, a auditoria:

- Não era para ser feita em todos os métodos ou em todas as alterações das tabelas.

- Não podia sobrecarregar o sistema, ou seja, era para acontecer sem ser perceptível ao usuário que chamava o serviço. (essa característica veio devido a uma tentativa anterior de colocar um framework de log de alterações em apenas uma das tabelas de alta demanda do sistema e disso ter ocasionado uma lentidão enorme e queda da aplicação em apenas alguns minutos)

- Caso ocorresse qualquer erro na auditoria, o método chamado deveria terminar a execução e efetuar a alteração normalmente.

- Seria uma auditoria de ações e não necessariamente de alterações de dados, sendo que até mesmo a visualização de certos dados poderia criar um registro de auditoria.

- Poderia ser feita em qualquer um dos diferentes sistemas da empresa, e inclusive ser gravado no mesmo banco ou em bancos diferentes. Nas mesmas tabelas ou em tabelas diferentes.

- Alterações em tabelas diferentes poderia ser gravada em uma mesma tabela de auditoria, ou alterações na mesma tabela poderia ser gravada em tabelas diferentes de auditoria. Tudo dependeria do que a ação representaria e essa escolha seria uma da decisão na modelagem.

-A auditoria deveria ser feita separadamente dos métodos das ações auditadas. Se possível nada do corpo do método seria alterado, nada dentro da chave ‘{’ que abre o método até a chave ‘}’ que o finaliza .

Enfim, a ideia era criar um sistema bem genérico e que pudesse atender diferentes demandas de negócio da empresa.

Será que algo assim era possível? Sim, foi.


A implementação

A modelagem de dados seguiu a ideia da criação de várias entidades de auditoria (ou tabelas). Todas elas com campos em comum, podendo também ter os seus próprios. Informações como o usuário que fez a modificação, IP do usuário, o evento feito, a data da realização, e o identificador do objeto que sofreu o evento são algumas das informações gravadas por padrão.

Como já foi adiantado, decidimos usar a programação orientada a aspectos. Presente desde o Java EE 6, o CDI com a utilização de Interceptors tinha o que precisávamos e se encaixou muito bem no problema. O fato de não ser necessário exportar outros pacotes ou frameworks foi um dos motivadores a mais para escolhê-lo. Assim a utilizamos na implementação da @Audit.

Exempo:

A interface da auditoria que permite a utilização posterior da notação @Audit em métodos:

@Inherited

@InterceptorBinding

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.METHOD, ElementType.TYPE})

public @interface Audit {

public String classeInterceptor;

@Nonbinding public String param1;

...

@Nonbinding public String param2;

}

Além disso foi criada uma classe abstrata para o interceptor, que já faz um bom processamento, guardando várias das informações em comum da auditoria. Todo projeto que usa a auditoria tem que implementar uma classe interceptor concreta estendendo a classe abstrata e declará-la no arquivo beans.xml. Essa classe é necessária para nos passar informações específicas como qual entidade vai ser auditada/persistida, como vai ser persistida, além de opcionalmente informações específicas de cada método a ser auditado, como o que foi modificado naquele método específico, qual é o valor anterior, as vezes até mesmo o valor posterior aquela ação:

Exemplo:

@Audit(classeInterceptor=”EntityInterceptor”)

@Interceptor

public class EntityInterceptor extends BaseAuditInterceptor {...}


No beans.xml

<interceptors>

<class>package.interceptorLoggedInterceptor</class>

</interceptors>

Grande parte da mágica está justamente nesse ponto, pois é como se o interceptor abraçasse os métodos que queremos auditar. Através deles podemos agir antes e/ou depois da ação, conseguimos pegar os parâmetros, podemos até mesmo mexer no retorno do método. É algo realmente incrível que abre um leque de possibilidades.

Muito importante também foi a orientação a objeto, com classes abstratas, interfaces, sobrecarga de métodos, generics, dentre outros. Isso foi usado em todos os pontos do projeto (desde a modelagem a chamada serviços) o que nos proporcionou facilidades e uma implementação centralizada (única em vários casos) na leitura, interceptação, integração, exibição e persistência dos dados.

Em alguns pontos também foi usada reflexão, com o Java Reflection.

Juntando tudo isso chegamos a uma solução que atendeu a todos os requisitos iniciais. E que pode ser usada em diversos pontos dos nossos sistemas com grande facilidade para o programador, que consegue fazer a auditoria customizada para cada ação com poucas linhas de código. Caso a entidade em questão já seja auditada no projeto, em certos casos, somente a notação @audit(pA=param1, p2=param2, ...) é suficiente para auditar um método novo no sistema.

Exemplo de uso antes de cada método auditado

@Audit(param1=param1, param2=param2, metodoExecutadoAntes= ”nomeMetodo”,metodoExecutadodepois=”nomeMetodo” ...)

public classeRetorno minhaAcao( Class usuarioResponsavel ou identificadorUsuarui, String ip, Long idEntidade, ClassNovaValor novoValor)

Informações como o ip, usuário que fez a modificação tem ser passados nos parâmetros do método auditado. O identificador da entidade a ser auditada é algo que é obrigatório na hora de persistir a auditoria, mas como existe o caso criar onde podemos não ter esse identificador antes da chamada da ação, ele pode ser passado como parâmetro do método auditado ou ser obtido depois no código a ser executado após o método auditado. Quando o método auditado é a alteração de algum campo (ou alguns campos), pode passar esse novo valor nos parâmetros do método auditado e/ou pegá-los no código a ser executado antes ou depois do método auditado.

Um detalhe dessa notação antes do método é que: caso ele tenha a notação @audit a chamada da auditoria será feita todas as vezes em que ele for chamado de uma classe diferente a que pertence. Mas e aqueles caso em que chamamos a ação para modificar algo, mas no fim nada é modificado? Podemos não gravar a auditoria? Sim, a auditoria foi implementada com um parâmetro específico que pode persistir a auditoria ou não somente quando há modificação na entidade auditada. No caso de visualizar dados sensíveis,onde nada é alterado, esse parâmetro deve ser desabilitado para persistir a auditoria

O principal motivo da criação da @Audit foi justamente poupar o trabalho do programador. Por exemplo, muita da repetição daquele mesmo código que só muda o alvo e tem o mesmo comportamento é evitada. Assim com ela, é possível inclusive criar uma nova entidade de auditoria com endpoints próprios, sem criar o seu controller, e esse “controller não criado” terá métodos que persiste, lista e etc, sem necessitar uma única linha de código para isso.

No fim, tenho uma paixão por essa funcionalidade. Esse foi um trabalho realizado com grande satisfação, muita pesquisa, aquele autêntico brilho nos olhos, e que me traz muito orgulho. Além disso, acredito que possa ser muito útil.

*Obs: Não coloquei um código de exemplo de uso detalhado aqui, mas caso alguém se interesse e queira esse código, pode entrar em contato comigo .

Nenhum comentário:

Postar um comentário