quinta-feira, 11 de abril de 2013

Utilizando Injeção para DAOs


Para o meu projeto do CadernoVigilantes, tive novamente de alterar a tabela EntradaPontos, que armazena os pontos armazenados, um pedido recorrente entre os usuários do sistema. A alteração foi  mudar o tipo de dados do campo Quantidade, da tabela Entrada_Pontos do tipo NUMBER para o tipo REAL (que é como o sqlite representa o float).

Adicionando uma modificação anterior, tenho agora três versões da tabela:

- Versão 1: A tabela Inicial.
- Versão 2: Alterando o valor em pontos de NUMBER para REAL.
- Versão 3: Alterando o campo quantidade de NUMBER para REAL.

Isto me fez alterar o método de atualização de tabela do DAO, onUpgrade, para levar em consideração mais esta possibilidade. O onUpgrade fará as seguintes operações:

- Se a versão do usuário for 1, atualize a tabela da versão 1->2.
- Se a versão do usuário for menor que três, atualize a tabela da versão 2->3.

Testar se a modificação está sendo feita corretamente é um pouco complicado. Como o acesso a base sqlite é realizado através do Context, que só existe em tempo de execução, eu ainda não vi uma forma simples de utilizar o JUnit para a automatização. Além disto, eu possuía apenas um objeto EntradaPontosDAO, que sofre modificações a cada versão de tabela, no método onUpdate, no método onCreate, e na função que transforma os dados em um objeto EntradaPontos.

Ou seja, para testes de regressão com versões anteriores do DAO, eu teria que garantir que a criação e atualização da tabela estava ocorrendo corretamente com os usuários, não importa qual a versão anterior deles. O que implicaria, a grosso modo:

1)  alterar o código para a versão anterior, injetar a apk no celula
2) adicionar dados
3)  modificar novamente o DAO para uma versão posterior
4)  injetar o apk novamente
5) verificar se o comportamento da atualização estava ocorrendo corretamente, e se a funcionalidade não estava afetada.

Tudo manualmente, com um grande componente de risco nas etapas 1 e 3. Foi o que eu fiz da versão 1 para a 2, só que agora as etapas teriam de ser repetidas da versão 1->2, 1->3 e 2->3 e novo 3, com boas chances de trazer uma versão do DAO errada do CVS em cada ponto. Esta obviamente não era uma boa opção.

Foi hora de usar as boas práticas. Primeiramente, eu fiz um refactoring da versão atual da classe EntradaPontosDAO para EntradaPontosDAO3. Então, eu extraí uma interface que representasse o EntradaPontosDAO3, que eu chamei de EntradaPontosDAO, que possuía apenas os métodos que estão sendo utilizados por objetos externos. Então, coloquei a EntradaPontosDAO3 como implementação do EntradaPontosDAO.


Isto me permitiu apontar para a interface, e não para o objeto de acesso a dados, o que é sempre uma boa prática. Isto me permitiu adicionar três novas classes, cada uma representando uma versão da tabela:


Então, agora só precisarei definir como realizar a injeção de qual DAO específico eu quero utilizar na minha classe de negócios. Para isto, utilizarei o RoboGuice.

Primeiramente, vou adicionar a notação de injeção nos construtores das três implementações, como no exemplo abaixo:


    @Inject
    public EntradaPontosDAO1(Context context) {
        super(context, TABELA, null, VERSAO);
    }

Isto indicará que o objeto DAO irá receber automaticamente o contexto do ambiente através do RoboGuice, conforme definido no ContextProvider (entrarei mais em detalhes sobre isto em outro post).

Deixando indicado no construtor da minha classe controladora que irá receber a injeção do DAO:

    @Inject
    protected ControleCaderno(EntradaPontosDAO entradaPontosDAO){
        super(entradaPontosSQL);
    }


Desta forma, quando o objeto de controle for criado, ele receberá automaticamente o objeto de acesso a dados, representado pela sua interface.

Finalmente, é só uma questão de definir na classe de módulo qual será a instância da interface que será injetada na interface:


public class ModuloCaderno extends AbstractAndroidModule {
    @Override
    protected void configure() {
        bind(EntradaPontosDAO.class).to(EntradaPontosDAO3.class);
        bind(ControleCaderno.class).asEagerSingleton();
    }
}

Qual foi a vantagem? Bom, agora eu tenho as três versões do DAO mantidas e estáveis, e apenas um ponto de amarração para definir qual das três classes será utilizada, com mínimo acoplamento. Tudo o que eu preciso para gerar um apk com os DAOs históricos 1,2 ou 3 é alterar o bind que é realizado no código acima para a classe desejada e regerar o pacote. O meu teste foi simplificado em muito, o risco diminuiu consideravelmente, e seguindo isto para os outros DAOs da minha aplicação, eu tenho uma aplicação mais simples de manter e testar, de forma geral. 

Nova Versão a Caminho - Mais Updates

Estou trabalhando na versão 1.4 do Caderno Vigilantes. Fiz um refactoring maior do código, tirando algum lixo acumulado, das versões anteriores, antes do uso do RoboGuice.

Está no planejamento da versão 1.4:
- Permitir a entrada de quantidades fracionárias (feito).
- Exportação do histórico de pontos.

Outras modificações sugeridas pelos usuários são desafios para uma versões posteriores:
- Gráfico de consumo de pontos.
- Widget de consumo de pontos.
- Adição de temas.
- Login e senha para armazenamento em um repositório.

Finalmente consegui disponibilizar o código no GIT, fica aqui: https://github.com/pcontop/CadernoVigilantes.git. O Intellij, a IDE que eu estou utilizando para o projeto, não permite o uso do CVS e do GIT ao mesmo tempo. Confiante de que a versão atual está razoavelmente estável, abdiquei do CVS para o GIT (naturalmente, eu tenho um backup guardado). Agora vou ter de adquirir experiência no uso da ferramenta nova.