6. Auditoria e Backup do Banco de Dados - gentil-eilison/Ankit-Backend GitHub Wiki
6. Auditoria e Logs das Operações no Banco de Dados
A auditoria e registro das operações realizadas dentro do banco de dados foi feita com a biblioteca django-simple-history. Ela foi escolhida por ser bastante poderosa, tendo diversas funcionalidades, como buscar uma instância de um modelo em uma data ou alteração específica, que se integram bem à API utilizada para manipular os modelos do Django, mesmo apresentando alguns pontos negativos.
Dessa forma, a auditoria será responsabilidade total da biblioteca, que realizará todas as operações necessárias para registrar as alterações nos modelos. Essas alterações são as de adição, alteração e remoção de tuplas do banco de dados. Efetivamente, cada tabela que representa uma entidade e que escolhemos fazer o registro das operações terá uma tabela equivalente com o prefixo nomeapp_historicalnomemodelo
, que irá armazenar os seguintes atributos:
Nome do atributo | Função |
---|---|
history_id | É o ID único do registro da alteração |
id | É o ID da tupla cuja alteração está sendo registrada |
history_date | Data e hora da alteração |
history_change_reason | Texto com o porquê da alteração. É opcional, possuindo valor NULL como padrão. |
history_type | Indica qual operação foi feita. Os valores possíveis são 3: + , indicando adição de uma tupla; - , indicando a remoção dela; e ~ , indicando uma alteração. |
history_user_id | ID do usuário que realizou a alteração |
Os atributos descritos na tabela acima estão presentes em todas as tabelas criadas pela biblioteca para o registro. Além deles, são armazenados também todos os atributos dos modelos cuja auditoria está sendo feito naquela tabela. Assim, será possível ter um snapshot do modelo no momento específico daquela operação. As alterações relativas a chaves estrangeiras que não necessitam de uma tabela intermediária também estão incluídas nesses atributos, contendo somente a chave primária da tabela que é referenciada. Entretanto, as relações muitos para muitos terão tabelas próprias para registrar as alterações. Essas tabelas são nomeadas seguindo o seguinte padrão: nomeapp_historicalnomemodelo_nomeatributom2m
. Ela conterá os atributos descritos na tabela abaixo:
Nome do Atributo | Função |
---|---|
m2m_history_id | Identifica a mudança de maneira única para cada par de chave estrangeira atual |
id | Identifica a mudança no relacionamento como um todo, indicando em qual mudança cada uma das chaves estrangeiras “entrou”. |
history_id | Identifica em qual mudança da tupla as chaves estrangeiras foram alteradas, referenciando o history_id da tabela histórica do modelo que tem, na sua definição, o campo ManyToManyField . Será igual para todas as tuplas que foram adicionadas ou removidas em uma mesma mudança. |
De maneira análoga a como acontece com as tabelas comuns, esses atributos estarão presentes em todas as relações responsáveis por registrar as mudanças nas chaves estrangeiras. Além delas, também são armazenados os ids dos dois modelos que estão se relacionando através dessa chave estrangeira.
Implementação no Código
Para utilizar a django-simple-history, basta, além da configuração básica, que seja criado um atributo e que a ele seja atribuído uma instância do HistoricalRecords
. A partir daí, a biblioteca irá fazer todo o trabalho de gerar as tabelas para o registro do histórico e implementar os métodos que garantam que as operações de CRUD sejam registradas nessas tabelas utilizando a API dos models do Django.
No entanto, como o history_user_id
possui uma restrição de chave estrangeira, é necessário tratar o caso de quando um usuário que já realizou alterações decide apagar a sua conta do banco de dados. Para isso, foi utilizado um signal do Django, que realiza essa pós-operação nas tabelas de histórico após o usuário ter sido deletado, trocando o usuário para None
. O ponto ruim dessa implementação é que, embora os registros do histórico permaneçam, o usuário que as realizou é perdido.
Outro ponto é que, quando alteramos um modelo, o pacote usado para registro de histórico não registra a mudança no modelo que representaria a chave reversa do relacionamento entre as tabelas. Para contornar esse problema, também foi utilizado um signal. Dentro ele, é chamado de maneira explícita o método save()
da tabela da chave reversa para que o registro também aconteça nela.
Ferramentas para o Administrador
A django-simple-history também permite que um superusuário administrador consiga visualizar esse histórico de mudanças na interface administrativa do Django, então será possível ter um administrador para verificar esses logs caso haja problema em alguma parte do sistema. Para que o administrador do sistema consiga visualizar esses dados, ele deve estar logado como usuário administrador e entrar na área administrativa da aplicação. Dentro dessa área, ele poderá visualizar o histórico de duas formas: o de uma tupla específica do banco de dados; e o de uma tabela como um todo.
Visualizando o Histórico de uma Tabela Como um Todo
Para visualizar o histórico de uma tabela, basta escolher a opção que possui Historical <Nome-Tabela>
dentro da interface administrativa, como mostra a imagem abaixo:
Ao selecionar uma delas, o histórico será exibido para o administrador. Na tela do histórico, é possível filtrar por um intervalo de data e hora da alteração, além de filtros específicos para a tabela em questão, caso haja. Além disso, ao detalhar um dos pontos do histórico, é possível ver detalhes importantes, como quem realizou a mudança e o tipo de mudança feita, seja criação, alteração ou modificação.
Visualizando Histórico de uma Tupla Específica
Para que o administrador consiga ver o histórico de uma tupla em específico, ele deverá entrar na listagem de tuplas da tabela cujos registros ele deseja verificar. Na interface administrativa, isso é feito por meio do clique no link cujo texto é NomedaTabela
. Após entrar na listagem das tuplas, ele deve selecionar a tupla cujo histórico é de interesse e clicar, no topo superior direito da página, no botão com o texto "History".
Na tela do histórico, ele poderá ver a lista completa dos pontos de modificação da tupla. A listagem conterá informações sobre o objeto em questão; tipo de alteração (criação, modificação, remoção); data e hora; e motivo da mudança.
Caso o administrador deseje reverter o objeto, do seu estado atual, para a versão registrada no histórico, ele pode fazer isso seguindo dois simples passos:
- Selecionando, dentro da listagem do histórico da tupla, qual versão no tempo para qual ele deseja reverter o objeto;
- Clicando no botão "Reverter" no final da página.
Backup de Dados de Forma Periódica
Ferramenta Escolhida
A implementação do backup do Banco de Dados do Ankit foi realizado utilizando a biblioteca django_dbbackup
, que facilita tanto a configuração em relação a onde o usuário quer realizar o backup (AWS, Google Cloud, localmente etc.) quanto a realização do backup, que pode ser personalizada com comandos como limpar velhos arquivos de backup durante sua realização e criptografá-los. No Ankit, o local de armazenamento dos backups escolhido foi uma pasta na raíz do projeto com o nome backups.
Implementação no Código
Para que o backup pudesse ser gerenciado em um painel por contas de administradores e também poder ser implementado de forma periódica e automática, tornou-se necessário ter o auxílio da biblioteca django_celery_beat
. A django_dbbackup
fornece comandos que podem ser utilizados com o manage.py
, como: (1) dbbackup
, utilizado para realizar o backup; e o (2) dbrestore
, para restaurar um banco utilizando um arquivo de backup. Flags como --clean
também podem ser utilizadas para limpar velhos backups.
Utilizando o Celery, foram criadas tarefas no arquivo tasks.py
, cada uma contendo comandos tanto para realizar backup do banco de dados quanto da pasta media
. Para chamar os comandos, foi utilizada a função call_command()
do módulo management
do Django, como mostra o código abaixo:
Com isso feito, qualquer usuário que possua uma conta de administrador pode agendar ou executar backups no painel de administração do Django.
O Painel de Administração
Executando um Backup
Para executar um backup de forma não automática e única, o administrador, primeiramente, vai ter que ir ao item Clocked da seção Periodic Tasks e clicar no botão Add, para registrar um período de execução de alguma tarefa do Celery (no caso, uma tarefa de backup):
Na tela seguinte, ele será apresentado com dois campos: a data e a hora da execução da tarefa. Ao preenchê-los e clicar no botão SAVE, você irá guardar esse período no banco de dados, como mostra a figura abaixo (tela para qual você será redirecionado(a)):
Em seguida, o usuário deve retornar para a página inicial e clicar no botão ADD do item Periodic Tasks para cadastrar uma tarefa do Celery:
Quando ele escolher um nome para a tarefa e escolher qual tarefa de backup ele deseja fazer nos campos Name e Task (registered), ele deve ir depois para a seção Schedule e escolher algum Clocked Schedule, que é o tipo de período o qual criamos, e selecionar a data e hora desejada, como mostra a figura abaixo:
Depois, ele deve marcar a caixinha ao lado do texto One-off Task, como na imagem abaixo:
Por fim, é só adicionar a tarefa clicando no botão SAVE. Vale lembrar que é possível excluir essa tarefa de backup depois, já que ela nunca mais vai ser executada; ou criar um novo período do tipo Clocked e editar a tarefa já criada com o novo período. Para realizar a deleção, basta selecionar a(s) tarefa(s) que deseja excluir, ir na caixa do opções de nome Action, escolher delete selected periodic tasks e clicar no botão Go:
Após isso, é só clicar no botão de confirmação que irá aparece na página seguinte:
Automatizando um Backup com Periodicidade
Além do período do tipo Clocked, é possível criar mais 3 tipos de períodos: Crontabs, em que pode-se especificar uma hora ou uma data específica para uma tarefa acontecer; Intervals, que possibilita a adição de um intervalo recorrente (a cada 30 segundos, por exemplo); e Solar Events, que especifica em qual evento solar o backup tem que ser executado, por exemplo. Nas imagens abaixo, encontram-se os campos que precisam ser preenchidos para criar cada um desses tipos de eventos, respectivamente:
O Crontab (imagem 2) permite criar um evento único com data e sem hora e vice-versa, que pode se repetir ou não por causa do valor *
, diferente do Clocked, que obriga o usuário a colocar uma data e uma hora. Isso significa que você pode usá-lo para criar tarefas que acontecem em um determinado horário do dia todos os dias, por exemplo.
Com um período do tipo Interval como exemplo, que permite colocarmos um certo intervalo para a execução de uma tarefa, como: a cada 30 segundos, a cada hora, a cada dia etc. podemos agora agendar uma tarefa para ser executada a cada 30 segundos. Para isso, o administrador precisa, novamente, ir para a seção de criar uma tarefa. Mas, dessa vez, ele terá que escolher uma periodicidade:
Como a periodicidade utilizada como exemplo foi uma do tipo Interval, será no campo Interval Schedule que a periodicidade que criamos irá se encontrar. Após escolhê-la, é só apertar no botão de adicionar novamente (ADD) e sua tarefa estará criada e rodando. A tarefa de backup na imagem abaixo tem o título de "Fazer Backup do Banco"