sexta-feira, 23 de junho de 2017

Você já parou para pensar sobre controle transacional ?

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Começo este post com a seguinte pergunta: você já parou para pensar sobre controle transacional ? Já tirou cinco minutos do seu dia para pensar no assunto ?

Minha pergunta pode parecer descabida ou até um pouco sentimental pois não fiz nenhuma apresentação sobre o tema. Contudo, faço essa pergunta porque em minha vivência de mercado já me deparei com muitos programadores que não tem a mínima idéia sobre o assunto. Obviamente eles sabem o que é uma transação, um commit ou um rollback. Porém pouquíssimos sabem implementar estruturas de controle transacionais do zero.

Interessante !

Tal fato além de ser extremamente preocupante na minha opinião, ocorre porque este tipo de questionamento não é mais necessário no mercado de trabalho atualmente. A maioria senão todos os projetos utilizam tecnologias que através da inversão de controle (IoC) implementam seu próprio controle transacional. Como resultado, oferecem interfaces bem definidas e limpas para seus usuários, tornando assim trabalho deles mais fácil e ágil. Aí está o ponto que me causa preocupação, o excesso de facilidade. Excesso o qual tende a tornar as pessoas, no caso desenvolvedores, mais preguiçosos causando um esquecimento de conceitos importantes.

Se você acabou de ler o parágrafo acima está pensando que estou falando de tecnologias como Spring, EJB 3 ou Hibernate. Acertou, estou falando exatamente disso ! Mas não vá pensar que estou condenando o uso de tais ferramentas. Pelo contrário, acho super correto utilizá-las e se você não as conhece, recomendo que estude-as. Entretanto, nunca se esqueça disso: não fique preso somente ao funcionamento das ferramentas existentes, entenda como elas funcionam ! O ponto central do meu raciocínio é justamente este, é necessário entender como “por detrás dos panos” o Spring, por exemplo, consegue executar rollback em uma transação inteira, onde nesta várias tabelas sofreram operações. Obviamente não podemos esquecer que ele não somente consegue fazer isso, mas como ainda disponibilizar uma forma que você escreva sua camada DAO (Data Access Object) de forma não acoplada a sua fachada de negócio, ou serviço.

A esta altura do campeonato você já deve ter notado que esta não é uma discussão trivial. Estamos lidando com uma situação arquitetural complexa com vários padrões de projeto, conceitos e preocupações. Logo algumas amostras de código e um diagrama de classes poderiam ajudar a trazer para a realidade o que estou propondo.
Figura 1 - Diagrama de classes proposto
É importante deixar claro aqui que o modelo proposto não é a solução para todos os problemas do universo. Como sempre digo, não existem balas de prata na computação. Talvez você leia esta postagem, encontre vários erros e monte um modelo melhor. Isso acontece, soluções desse tipo são de caráter evolutivo. Então, fique a vontade para contribuir.

Começaremos a explicação deste modelo analisando a classe DAO. Como o nome já mostra, ela segue o padrão DAO (Data Access Object), logo sua responsabilidade é lidar com abstrações de acesso à dados na base de dados. Trabalhando em conjunto com ela existe a classe HelperDAO, e como o seu nome também deixa claro, ela é uma classe auxiliar. Sua responsabilidade é a de prover métodos auxiliares de fechamento de statements e result sets. A interface ConnectionInjector possui um único método, responsável pela injeção da instância do objeto de conexão de banco de dados na classe DAO. Esta injeção de fato ocorre pela implementação da interface pela classe. Mais a frente entenderemos o motivo disso.

As classes que tratam o negócio são representadas pela classe BusinessFacade e é nela que todo o controle transacional acontece. Ali deve estar o nosso foco. Neste momento você pode estar se questionando da seguinte forma: se um contexto transacional é relacionado diretamente a operações de banco de dados, porque implementá-lo na camada de negócio ? Isto não tornaria a implementação altamente acoplada ?

Não te condeno por tal dúvida, pois eu já a tive algum tempo atrás, mas refletindo sobre assunto cheguei a conclusão de que eu estava errado. O negócio é o elemento que dita o que deve estar ou não na transação, por exemplo: toda vez que um usuário for cadastrado no sistema X, ele deve ser imediatamente cadastrado na lista de usuários que tem dívidas em aberto. Então em nossa transação devem existir instruções de escrita nas tabelas de usuários e de devedores. Caso haja algo de errado nesta operação como um todo, todas as operações devem ser desfeitas. Agora imagine se para cada operação uma nova transação fosse aberta, como controlar todo o contexto de negócio ? Se uma operação falhar como comunicar a próxima ou a anterior ? Essas são as reais preocupações que devem tomar a mente de um desenvolvedor/arquiteto quando ele planeja montar seu contexto transacional.

Para que a classe de negócio não tenha mais de uma responsabilidade, fato que fere o primeiro princípio da S.O.L.I.D, ela utiliza a ajuda de uma classe helper, ou seja, uma classe auxiliar. É aí que a classe HelperTransactionalControl ganha espaço, pois nela estão concentradas todas as funções necessárias para se criar ou não um contexto transacional, e caso criado, controlá-lo. Lembre-se que leituras em banco podem ser feitas e não há a necessidade de uma transação para isso. 

Um ponto interessante nesta classe é o método injectConnection(ConnectionInjector…). Note que ele recebe um varargs do tipo ConnectionInjector e que esta é a interface que todas as classes DAO implementam. Na implementação deste método ele faz uma chamada ao método injectConnection(Connection) da interface ConnectionInjector, ou seja, ele injeta um objeto de conexão em todas as instâncias DAO utilizadas. Então, na classe de negócio a única preocupação que deve ser considerada é a de invocar este método da classe helper, passando como parâmetro todos as instâncias DAOs utilizadas no momento. Fiz essa escolha de projeto pois não me agradava o fato de talvez passar como parâmetro, no construtor de todas as instâncias DAOs, um objeto do tipo Connection. Não é responsabilidade da classe que trata o negócio lidar com esse objeto, logo escolhi fazer isso “por debaixo dos panos”.

A interface que a classe de negócio implementa é uma simples escolha de programação orientada a interface. Com este recurso posso facilmente trocar a implementação de um contexto de negócio sem ter que me preocupar com quem já consumia informações a partir dele.

--------------------------------------------------------------------------------------------------------------------------
OBSERVAÇÃO: O varargs da linguagem Java é uma categoria de tipo de dados que se comporta como se fosse um array. Logo, se o seu método recebe um varargs do tipo inteiro, você pode passar os valores tanto separados por vírgula como por um array, exemplo: foo(1,2,3,4,5) ou foo(new int[]{1,2,3,4,5}).
--------------------------------------------------------------------------------------------------------------------------

Acredito que com as amostras de código abaixo tudo o que foi discutido ficará mais claro.

ConnectionInjector.java


1
2
3
4
5
6
7
package src.model.dao;
 
import java.sql.Connection;
 
public interface ConnectionInjector {
 void injectConnection(Connection connection);
}

Classe HelperDAO.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package src.model.dao.helper;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
 
public class HelperDAO {
 
 public void closeStatement(Statement statement)
   throws SQLException {
  if (statement == null) {
   throw new IllegalArgumentException(
     "Errorcode: 102 - Statement nulo.");
  }
  try {
   statement.close();
  } catch (SQLException e) {
   throw new SQLException("Errorcode: 102 - Erro no fechamento do objeto Statement.", e);
  }
 }
 
 public void closeStatementAndResultSet(Statement statement,
   ResultSet resultSet) throws SQLException {
  this.closeStatement(statement);
  if (resultSet == null) {
   throw new IllegalArgumentException(
     "Errorcode: 102 - ResultSet nulo.");
  }
  try {
   resultSet.close();
  } catch (SQLException e) {
   throw new SQLException("Errorcode: 102 - Erro no fechamento do objeto ResultSet.", e);
  }
 }
}


DAO.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package src.model.dao;
 
import src.model.dao.helper.HelperDAO;
import java.util.List;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
 
public class DAO implements ConnectionInjector{
 private Connection connection;
 private HelperDAO helperDAO;
 
 public DAO(){
  this.helperDAO = new HelperDAO();
 }
 
 @Override
 public void injectConnection(Connection connection) {
  this.connection = connection;
 }
 
 public List findAll() throws IntegrationException {  
  PreparedStatement preparedStatement = null;
  ResultSet resultSet = null;
  List results = new ArrayList<>();
  try{
   String sql = "SELECT COLUMN1, COLUMN2, COLUMN3 FROM TABLE";  
   preparedStatement = this.connection.prepareStatement(sql);
   resultSet = preparedStatement.executeQuery(sql);
   while (resultSet.next()) {
    MyObject myObject = new MyObject();
    myObject.setMyProperty1(resultSet.getLong("COLUMN1"));
    myObject.setMyProperty2(resultSet.getString("COLUMN2"));
    myObject.setMyProperty3(resultSet.getString("COLUMN3"));
    
    results.add(myObject);
   }
   return results;
  } catch (SQLException e){
   throw new SQLException("Errorcode: 104 - Erro de SQL na listagem de todos os objetos.", e);
  } finally {
   helperDAO.closeStatementAndResultSet(preparedStatement, resultSet);
  }
 }
 
 private void insert(MyObject myObject) throws SQLException {
  PreparedStatement preparedStatement = null;
  try {
   int paramPos = 0;
   String sql = "INSER INTO TABLE (COLUMN1, COLUMN2, COLUMN3) VALUES (?,?,?)");
   preparedStatement = this.connection.prepareStatement(sql);
   preparedStatement.setLong(paramPos++, myObject.getProperty1());
   preparedStatement.setString(paramPos++, myObject.getProperty2());
   preparedStatement.setString(paramPos++, myObject.getProperty3());
   preparedStatement.execute();
  } catch (SQLException e) {
   throw new SQLException(
     "Errorcode: 104 - Erro de SQL no cadastro do objeto.",
     e);
  } finally {
   helperDAO.closeStatement(preparedStatement);
  }
 }
}


HelperTransactionalControl.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package src.model.business.helper;
 
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
 
import src.model.dao.ConnectionInjector;
import src.model.exception.IntegrationException;
 
public class HelperTransactionalControl {
 
 private static final String USER_DATABASE = "USER";
 private static final String PASSWORD_DATABASE = "PASSWORD";
 
 private Connection connection;
 
 public void openConnection() throws IntegrationException {
  try {
   Class.forName("com.jdbc.mysql.Driver");
   this.connection = DriverManager.getConnection(
     "jdbc:mysql://localhost:3306/DATABASE",
     USER_DATABASE, PASSWORD_DATABASE);
  } catch (SQLException | ClassNotFoundException e) {
   throw new IntegrationException("Errorcode: 100 - Erro na criação de uma conexão ao banco de dados.", e);
  }
 }
 
 public void injectConnection(ConnectionInjector... daosObject){
  for (ConnectionInjector daoObject : daosObject) {
   daoObject.injectConnection(connection);
  }
 }
 
 public void endConnection() throws IntegrationException {
  if (connection == null) {
   throw new IllegalArgumentException(
     "Errorcode: 102 - Connection nula.");
  }
  try {
   connection.close();
  } catch (SQLException e) {
   throw new IntegrationException("Errorcode: 102 - Erro no fechamento do objeto Connection.", e);
  }
 }
 
 public void beginTransaction() throws IntegrationException{
  try {
   connection.setAutoCommit(false);
  } catch (SQLException e) {
   throw new IntegrationException("Errorcode: 103 - Erro na abertura da transação de banco.", e);
  }
 }
 
 public void commitTransaction() throws IntegrationException{
  try {
   connection.commit();
  } catch (SQLException e) {
   throw new IntegrationException("Errorcode: 103 - Erro na commit da transação de banco.", e);
  }
 }
 
 public void rollbackTransaction() throws IntegrationException{
  try {
   connection.rollback();
  } catch (SQLException e) {
   throw new IntegrationException("Errorcode: 103 - Erro no rollback da transação de banco.", e);
  }
 }
}


BusinessFacade.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package src.model.business;
 
import java.sql.SQLException;
import java.util.List;
 
import src.model.dao.DAO;
import src.model.exception.IntegrationException;
import src.model.business.helper.HelperTransactionalControl;
 
public class BusinessFacade implements IBusinessFacade{
 
 private HelperTransactionalControl helper;
 
 public BusinessFacade() {
  helper = new HelperTransactionalControl();
 }
 
 @Override
 public MyObject insert(MyObject myObject) throws IntegrationException {
  try {
   DAO dao = new DAO();
   
   helper.openConnection();
   helper.beginTransaction();
   helper.injectConnection(dao);
   dao.insert(myObject);
   helper.commitTransaction();
   
   return myObject;
  } catch (SQLException e) {
   helper.rollbackTransaction();
   throw new IntegrationException(e);
  } finally {
   helper.endConnection();
  }
 }
 
 @Override
 public List findAll() throws IntegrationException{
  try {
   DAO dao = new DAO();
      
   this.helper.openConnection();
   this.helper.injectConnection(dao);
   return dao.findAll();
  } catch (SQLException e) {
   throw new IntegrationException(e);
  } finally {
   this.helper.endConnection();
  }
 }
}


Note que como consequência da implementação do projeto de software proposto, a classe de tratamento do negócio ficou bem enxuta e com responsabilidades bem definidas. Não achei necessário expor aqui o código-fonte da exceção especializada (IntegrationException) e da interface IBusinessFacade porque seus conteúdos são bastante triviais e iriam causar mais volume na postagem, talvez tornando a leitura mais cansativa.

Como dito anteriormente, sinta-se livre para criticar, existem várias formas de se fazer o mesmo e é isso que engrandece um projeto de software, mas se você quiser elogiar não se acanhe 😋.

Chegamos ao fim de mais um post e neste aqui devo grandes agradecimentos a um amigo e mentor, o imenso Luís Marcelo Bruckner. Muito obrigado meu amigo.

Até a próxima minha boa gente ! 😘
Leia nossa postagem anterior: Grafos ponderados - Grafos
Download do código-fonte
GoogleDrive: clique aqui 
Dropbox: 
Dúvidas !? Sugestões ?! Críticas ou elogios ?!
Deixe aí nos comentários, envie um e-mail ou uma mensagem na nossa página do Facebook.
E-mail: precisoestudarsempre@gmail.com
Leia Mais ››