Erros que você provavelmente comete: veja as melhores práticas para controle de transações
Em um sistema moderno, é comum que tenhamos interações entre diferentes fontes de dados que devem ser coordenadas para produzir resultados atômicos. Normalmente, tais fontes de dados são sistemas de bancos de dados relacionais e a coordenação de resultados entre elas é realizada através de transações.
Mas qual a melhor forma de garantir que estes resultados sejam mesmo atômicos? Como fazer com que problemas em uma das fontes de dados façam com que as operações realizadas em outras fontes também sejam desfeitas? Como evitar com que a transação criada seja acoplada entre camadas da aplicação? É possível transacionar outras fontes de dados que não sejam bancos de dados?
Estas perguntas são todas respondidas com a utilização das entidades contidas no pacote System.Transactions, parte integrante do .NET Framework desde sua versão 2.0, principalmente sua classe TransactionScope.
É normal, em sistemas pequenos, com uma única fonte de dados e pouquíssimas interações em cada operação de negócio, que uma fonte de dados de sistemas de banco de dados (daqui para frente referenciada pela sua interface, IDbConnection), simplesmente crie uma nova transação ao chamar a operação IDbConnection.BeginTransaction(). Esta operação gera uma IDbTransaction e garante que as operações realizadas no banco serão completadas ao executar a operação IDbTransaction.Commit(). Esta abordagem requer que, para uma determinada operação de negócio, a instância de IDbConnection e de IDbTransaction sejam únicas no contexto de execução desta operação, garantindo que todos os acessos a banco passem por estas mesmas instâncias. Somente assim esse método de negócio será realmente transacional.
Sumário
E aí? Resolvido?
Quase. O problema desta solução é que, se há operações sobre fontes de dados sendo executadas em várias classes dentro do contexto da operação de negócio, as instâncias da conexão e da transação normalmente devem ser passadas como parâmetro para todas estas classes, causando um dos maiores pecados que podem ser cometidos na programação orientada a objetos: o acoplamento. Isso faz com que a assinatura de todas as classes que participam dessa operação de negócio “exponham” parte de sua implementação, tornando difícil o reuso e a manutenção da mesma, assim como uma possível substituição da camada de acesso a dados.
Mas calma, nada é tão ruim que não possa piorar: imagine que sua aplicação possua duas (ou mais) fontes de dados. É fácil perceber como o acoplamento aumenta de forma drástica (duplicando o número de instâncias de IDbConnection e de IDbTransaction para cada fonte de dados adicional), além de causar outro problema ainda mais sério: cada fonte de dados tem sua transação específica, o que quer dizer que, se a primeira for executada com sucesso e a segunda falhar, apenas a segunda será desfeita, perdendo o conceito de operação atômica.
E agora?
Uma melhoria na implementação de transações acima seria a utilização de um Contexto de execução. Ao iniciar a execução de uma operação de negócio, um Contexto estático (por Thread de execução) é criado. Assim, sempre que uma classe necessitar de acesso a uma instância de IDbConnection, a responsabilidade de obtenção da IDbConnection desejada deve ser delegada ao Contexto associado à Thread atual. O Contexto deve ser responsável por manter uma lista atualizada das conexões em uso e criar uma nova sempre que for a primeira requisição pela conexão desejada. Da mesma forma, se uma conexão deve ser transacionada, então o Contexto deve ser responsável por iniciar a transação e sempre utilizar essa mesma transação dentro da operação de negócio. Assim, temos um repositório central que cuida da criação/manutenção das entidades necessárias.
Agora sim acoplamento resolvido, correto?
Na verdade, ainda não. Nós apenas centralizamos os pontos que fazem acesso direto às interfaces de IDbConnection e IDbTransaction, mas ainda há uma dependência forte das classes para o próprio Contexto. Outro problema desta abordagem é que a complexidade de se manter uma lista válida/atualizada de IDbConnection e IDbTransaction se torna responsabilidade do Contexto, dificultando a capacidade de manutenção do código. Sem falar que o problema de duas fontes de dados em uma mesma operação de negócio não transacionais entre si ainda persiste.
É aí que entra a classe TransactionScope. Ao criar uma instância dessa classe (normalmente utilizando as boas práticas da construção “using”), é garantido que todo o código dentro deste bloco estará contido em uma transação. Obteve uma nova instância de IDbConnection e não chamou BeginTransaction? Sem problema. As implementações mais atuais da maior parte dos drivers de acesso a banco de dados ADO.NET já é integrada à solução do TransactionScope por “debaixo dos panos”: o que a TransactionScope faz é disponibilizar uma instância de uma transação (não mais uma IDbTransaction, mas sim uma ITransaction) na Thread corrente em um local conhecido e as implementações dos drivers (ou qualquer Resource Manager, que é a forma como a MSDN se refere a fontes de dados que suportam transações – não somente fontes de dados de sistemas de banco de dados) conseguem “ver” esta transação e se registrar (ou enlist, no original em inglês) nela.
Parece familiar?
É basicamente a solução de Contexto acima, com a primeira vantagem sendo que a responsabilidade de manutenção das conexões/transações foi delegada para os resource managers/TransactionScope, facilitando a vida do desenvolvedor. Outra vantagem: através de um mecanismo conhecido como Coordenador de Transação Distribuída (DTC, no original em inglês), é possível que duas fontes de dados diferentes compartilhem da mesma transação, fazendo com que ambas se tornem verdadeiramente transacionais entre si. Obviamente, a participação em uma transação DTC é bem mais custosa, mas o pacote System.Transactions é inteligente o bastante para somente promover uma transação para DTC caso necessário. Para participar em uma transação DTC, é necessário que o Resource Manager da fonte de dados suporte a participação nesse tipo de transação.
Bacana, não é? Segue um exemplo bem básico (que desconsidera separações em camadas e outras boas práticas) em pseudo-código:
public void OperacaoDeNegocio(parâmetros)
{
using(TransactionScope escopo = new TransactionScope())
{
ExecutarPrimeiraOperacao();
ExecutarSegundaOperacao();
// Se não houve erro em nenhuma das duas operações, marca a operação como concluída. É aqui que acontece o Commit da transação.
escopo.Complete();
}
}
private void ExecutarPrimeiraOperacao()
{
using(IDbConnection conn = new SqlServerConnection(“ConexaoSQLServer”))
{
IDbCommand comando = conn.CreateCommand();
comando.Execute(“INSERT INTO usuario VALUES (‘Eduardo Lima’)”);
}
}
private void ExecutarSegundaOperacao ()
{
using(IDbConnection conn = new AseConnection(“ConexaoSybase”))
{
IDbCommand comando = conn.CreateCommand();
comando.Execute(“UPDATE execucoes SET numeroExecucoes = numeroExecucoes + 1 WHERE idExecucao = 1”);
}
}
Veja que, acima, não foi preciso explicitamente criar transações nem ficar definindo e reaproveitando conexões (trabalhos estes de responsabilidade da TransactionScope e de cada conexão). Dessa forma, ambas as fontes se tornam transacionais entre si, com um nível de acoplamento baixo. Com uma boa implementação da obtenção da fonte de dados, é possível reduzir o acomplamento a um nível mínimo, facilitando a troca da fonte de dados (por exemplo, trocando o banco de dados Sybase por um Oracle).
O pacote System.Transactions possui várias outras vantagens, como permitir que sejam implementados Resource Managers através da implementação de interfaces do próprio pacote, ou permitir que serviços WCF sejam marcados como transacionais através de alguns atributos. Obviamente há vários outros aspectos relacionados ao uso do pacote System.Transactions e suas vantagens, que não cabem nesse post. Um grande ponto de partida é o artigo do MSDN https://msdn.microsoft.com/en-us/library/ms973865.aspx, leitura obrigatória para quem deseja se aprofundar no assunto.
Vejam que, de forma simples, utilizando apenas recursos nativos do framework .NET, é possível implementar um controle transacional robusto e escalável, refletindo, assim, um dos grandes valores da DTI: o de sempre desenvolver soluções aderentes aos principais padrões de mercado, sem a necessidade de “reinventar a roda”, priorizando assim a agilidade e mantendo a qualidade das entregas para o cliente.
Por: Eduardo Lima e Jéssica Saliba.
Produtos Digitais
Confira outros artigos
![Produtos digitais: implantação e operação](/_next/image?url=https%3A%2F%2Fwww.cms.dtidigital.com.br%2Fwp-content%2Fuploads%2F2024%2F06%2FProdutos-digitais-implantacao-e-operacao.webp&w=1024&q=75)
Produtos digitais: entregas contínuas com IA
Nas últimas semanas, lançamos uma série de artigos sobre o uso de inteligência artificial no processo de construção de produtos digitais. Neles, apresentamos alguns aceleradores que a dti tem utilizado para potencializar a eficiência dos times. Abordamos a fase de concepção do produto, as atividades de gestão e design, o desenvolvimento do software e a […]
Produtos Digitais
![Inteligência Artificial: acelerando o design e gestão de produtos digitais. Computador com códigos coloridos](/_next/image?url=https%3A%2F%2Fwww.cms.dtidigital.com.br%2Fwp-content%2Fuploads%2F2024%2F05%2Fshutterstock_2284126663-_1_-scaled.webp&w=1024&q=75)
Inteligência Artificial: acelerando o design e gestão de produtos digitais
Como aproveitar o melhor da Inteligência Artificial Generativa para gerar mais valor? Essa tem sido uma pergunta recorrente no mercado conforme as empresas buscam entender e adotar a tecnologia. Embora existam muitas dúvidas e hipóteses não comprovadas, parece ser consenso que os avanços na Inteligência Artificial impactarão significativamente muitas profissões. No relatório The economic potencial […]
Produtos Digitais
![IA Generativa: acelerando a concepção de produtos digitais. Foto de quadro com post-it e gráficos, numa ideação para conceber o produto](/_next/image?url=https%3A%2F%2Fwww.cms.dtidigital.com.br%2Fwp-content%2Fuploads%2F2024%2F05%2Fjanela-planejamento0.webp&w=1024&q=75)
IA Generativa: acelerando a concepção de produtos digitais
Como as empresas podem responder às crescentes demandas por produtividade e eficiência em um mundo digital em constante mudança? O período pandêmico acelerou a digitalização de várias empresas e agora o desafio é outro. Entramos em uma era de incertezas, na qual não há mais espaço para desperdícios ou prolongados ciclos de entrega de software. […]
Produtos Digitais