segunda-feira, 23 de setembro de 2013

Inversão de Controle e Injeção de Dependências

Quando comecei a programar na escola (curso técnico que fiz na época), comecei em uma das linguagens mais legais e, de certo modo, estranha que existe, a linguagem C. Alguns conceitos dela não são claros logo de inicio, pois não fazem nenhum paralelo com o mundo real. Um deles é o conceito de ponteiros e alocação de memória, onde você dinamicamente pode alocar memória para seu programa utilizar. Porém, como dizia o Tio Ben: "Grandes poderes trazem grandes responsabilidades", logo você se via obrigado a "lembrar" de liberar toda a memória alocada quando não fizesse mais uso dela, senão seu programa logo travaria junto com o seu sistema operacional.

O mundo mudou, os tempos agora são outros, temos a internet, o relógio digital e o Garbage Collector, que faz toda a gerência da coleta de memória que não é mais utilizada por nossos objetos. Porém agora temos que gerênciar outros recursos, como objetos que abrem arquivos e devem fecha-los ou conexões com o banco de dados. Caso você não os feche, seu arquivo não poderá ser aberto novamente ou seu banco de dados começará a recusar conexões, e sinceramente, isso é muito chato.


Por isso, vamos lembrar de fechar todas as nossas conexões nos nossos DAOs:

public class CarroDao {
    private Connection connection;

    private String INSERIR_CARRO = "INSERT INTO carros(nome, marca) VALUES(?, ? )";
    private String EXCLUIR_CARRO = "DELETE FROM carros WHERE id = ?";
    private String RECUPERAR_CARRO = "SELECT id, nome, marca FROM carros WHERE id = ?";

    public CarroDao() {
        connection = ConnectionFactory.getConnection();
    }

    public void inserir(Carro carro) {...}

    public void excluir(int id) {...}

    public void recuperar(int id) {...}

    private void fecharRecursos(ResultSet rs, PreparedStatement ps) {...}
    
    public void fechaConexao() {
        try {
            connection.close();
        } catch(SQLException e) {
            System.err.println("Problemas ao fechar a conexao: " + e.getMessage());
        }
    }
}
O nosso código principal, que faz uso da classe CarroDao:
public class AppMain {

    public static void main(String[] args) {
        Carro carro = new Carro();
        carro.setNome("Time Machine");
        carro.setMarca("DeLorean");
        
        CarroDao carroDao = new CarroDao();
        
        carroDao.inserir(carro);
        
        carroDao.fechaConexao();
    }

}
Até este momento, não teremos problemas, porém podemos agora tentar utilizar novamente o carroDao  e realizar alguma outra operação:

public class AppMain {

    public static void main(String[] args) {
        //...Codigo anterior
        
        //Outra operação
        carroDao.excluir(1);

        carroDao.fechaConexao();
    }
}
E funciona normalmente, mas se mantivermos esse padrão e criarmos outro Dao, por exemplo, PessoaDao, que também instanciará uma Connection para ela e também possuirá o método fecharConexao, que também devemos lembrar de fechar:

public class AppMain {

    public static void main(String[] args) {
        //...Codigo anterior
        
        //Outra operação
        carroDao.excluir(1);
        
        carroDao.fechaConexao();
        
        Pessoa pessoa = new Pessoa("Marty McFly");
        
        PessoaDao pessoaDao = new PessoaDao();
        pessoaDao.inserir(pessoa);
        
        pessoaDao.fecharConexao();
    }
}
Perceba que até o momento criamos dois DAOs e também duas conexões com o banco de dados, cada DAO contendo a sua conexão, porém como é de boa prática, não devemos criar conexões indiscriminadamente com o banco, sendo a melhor solucão usar um pool de conexões e também caso as operações estejam em um mesmo contexto, utilizar a mesma conexão para realizar todas as operações e no final, fechar ela.

Porém como podemos fazer isso de maneira limpa e simples? Poderiamos dar um getConnection do CarroDao e recuperar a conexão dele e usar para PessoaDao também, mas e se usarmos o PessoaDao sem querermos utilizar o CarroDao, o que faremos? O termo DAO vem de Data Access Object, sendo utilizado para realizar a recuperação dos dados e a conversão em objetos, deixando claro que não é de sua responsabilidade criar conexões e acessar os recursos.

De maneira mais limpa, podemos simplesmente alterar os nossos DAOs para receberem a conexão dos seus clientes, deixando a cargo deles a criação e manutenção da conexão com o banco. Porém os DAOs precisam obrigatoriamente receber a conexão, por isso vamos colocar Connection como parâmetro do construtor dos DAOs e podemos remover os métodos fecharConexao:

public class CarroDao {
    private Connection connection;

    public CarroDao(Connection connection) {
        this.connection = connection;
    }
    //...metodos de manipução já existentes, e removido o método fecharConexao
}
Nossa classe  CarroDao agora está bem mais coerente com o seu papel, agora devemos criar a conexão dentro do código cliente, utilizando ela onde for necessário:

public class AppMain {

    public static void main(String[] args) {
        Carro carro = new Carro();
        carro.setNome("Time Machine");
        carro.setMarca("DeLorean");

        Connection connection = ConnectionFactory.getConnection();

        CarroDao carroDao = new CarroDao(connection);

        carroDao.inserir(carro);

        carroDao.excluir(1);

        Pessoa pessoa = new Pessoa("Marty McFly");

        PessoaDao pessoaDao = new PessoaDao(connection);
        pessoaDao.inserir(pessoa);

        try {
            connection.close();
        } catch(SQLException e) {
            System.err.println("Problemas ao fechar a conexao: " + e.getMessage());
        }
    }
}
Com apenas uma conexão conseguimos realizar todas as nossas operações, e facilmente também podemos transacionar a nossa operação, caso ocorra algum problema, realizamos o rollback de todas as operações anteriores que utilizaram a mesma conexão e devido a isto estão no mesmo contexto.

O nome deste padrão de desenvolvimento é Inversão de Controle, onde você repassa o gerenciamento de recursos um nível acima e recuperando os mesmos onde for necessário através de injeção de dependência (que no nosso caso, obrigamos a passar pelo construtor).

A inversão de controle e a injeção de dependências (em inglês, Inversion of Control - IoC, e Dependency Injection - DI, respectivamente), além de permitirem que tenhamos classes com menos responsabilidades e o mais interessante, que possamos gerenciar facilmente objetos que acessam recursos externos. No nosso caso, a conexão estava sendo gerenciada direto pela aplicação exemplo (AppMain), porém podemos facilmente deixar a cargo de algum framework ou container de aplicação este gerenciamento, e nuances como o local do banco de dados, usuários e senhas, serem facilmente configuradas por XMLs ou annotations.

No futuro teremos um artigo mostrando como usar a injeção de depedências com o framework mais conhecido para esta tarefa, o Spring, mostrando sua configuração básica e modo de operação.

Nenhum comentário:

Postar um comentário