Mind Bending

Como afirmei diversas vezes, a DAL do web2py é fantástica. Entretanto, ela tem diversas funcionalidades que não são muito divulgadas. Por exemplo, você sabia que todas as transações de banco que a DAL (ou o próprio framework Web2py) executa, elas são agrupadas em transações? Isso é bom, pois garante a "modularidade" das mudanças de banco e torna possível a "reversão" (rollback) em caso de erro.

web2py

Entretanto, quando você manipula o banco diretamente todas as alterações são consideradas como estando dentro de uma mesma transação, e um rollback resultaria na "reversão" de dados que estavam corretos. Complicou? Vamos com calma.

Todas as transações de create, drop, insert, truncate, delete, ou update (realizadas por você dentro de um controller) não são "enviadas" para o banco imediatamente até o fim da execução. Esta forma de implementação visa simplificar o conceito de "transações", tornando cada controller o container de uma transação.

A implementação deste "container" é implementada da seguinte forma, ao iniciar um controller, o web2py inicia uma transação. Ao fim deste controller, o próprio web2py faz o commit das alterações e, em caso de qualquer exceção (exceto HTTP exceptions) o framework realiza o rollback da transação. Desta forma o framework te poupa do trabalho capturar exceções, realizar rollbacks e commits. Essa abordagem resolve 90% dos casos, para os outros 10% devemos realizar uma pequena intervenção.

Digamos que na sua aplicação em algum momento você precisa realizar uma dada alteração (automatizada e em background) em diversos clientes e, em caso de erro, reverter as alterações apenas deste cliente e seguir em frente, registrando os erros em uma tabela para análise posterior. Para isso podemos utilizar a seguinte estrutura:

import datetime

def atualiza_clientes():
    registros = db(
            (db.cliente.id == db.mensalidade.cliente) &
            (db.mensalidade.vencimento < datetime.date.today())
            ).select()
    for registro in registros:
        cliente = registro.cliente
        mensalidade = registro.mensalidade
        if mensalidade.pagamento:
            continue
        try:
            cliente.update_record(inadimplente=True)
            mensalidade.update_record(suspensa=True)
        except Exception as e:
            db.rollback()
            contexto = "Suspendendo cliente %s"%cliente.nome
            db.alertas.insert(contexto=contexto,
                        mensagem=e.message,
                        )
        else:
            db.commit()

Notem que toda a atualização de banco que corre o risco de ocasionar um erro está sendo executada dentro de um bloco try ... except. Neste caso, já que é só um exemplo, eu estou capturando todos os tipos de erro (o que não é uma boa prática). Para as alterações realizadas com sucesso (bloco else) apenas realizamos o commit.

Esta forma de processamento garante que um erro não irá abortar a execução do controller. E da mesma forma, garante que, após alterar o cliente com sucesso, um erro na atualização da mensalidade não resultará no uma tabela inconsistente, pois ao realizarmos o rollback desfazemos todas as alterações no banco desde o último commit.

Como podem ver, o web2py já provê uma forma de operação que banaliza a necessidade realizar commits e rollbacks, mas em dados momentos, este conhecimento pode ser extremamente útil.

Até a próxima…

Magnun

Magnun

Engenheiro de telecomunicações por formação, mas trabalha com suporte à infraestrutura GNU/Linux, e nas horas vagas é Programador OpenSource (Python e C) desenhista e escritor do Mind Bending Blog.


Comments

comments powered by Disqus