Ismael Soares fez uma pesquisa sobre refatoração de banco de dados no programa Bluesoft Labs, essa é segunda parte de sua pesquisa, e é com muito orgulho que a Bluesoft compartilha com você, não deixe de conferir também os slides.
No artigo Refatoração de Banco de Dados, ficou claro que Database Refactoring  é uma técnica de implementação para banco de dados e pode ser aplicada a qualquer momento de um projeto. No entanto, existem estratégias e procedimentos que precisam ser seguidos para que tal implementação seja bem sucedida. Veja alguns exemplos da aplicação desta técnica neste artigo.

Antes de começar…

Antes de qualquer coisa, um projeto de refatoração de banco de dados, é preciso ter em mente que nenhuma estrutura deve ser tão rígida a ponto de ser inalterável, isto porque pequenas melhorias sempre acontecerão.
Divida o seu projeto em etapas pequenas para facilitar o controle e a compreensão de todos os envolvidos.
É um erro tentar enxergar as refatorações de uma forma global. É preciso identificar a refatoração de cada objeto de forma individual.
Ao calcular o tempo necessário para as transições, tome cuidado para não subestimar as tarefas.
Evite duplicações de códigos SQL. Utilize um framework de persistência para encapsular o acesso ao banco de dados.

Qual a melhor estratégia?

Não existe uma resposta exata para esta pergunta. Uma série de fatores pode influenciar: arquitetura, modelo de dados, quantidades de sistemas externos, relacionamentos entre objetos internos (triggers, procedures, functions), etc. No entanto, qualquer estratégia adotada deve ter como base os seguintes passos:
  1. Avaliação do impacto: antes de fazer qualquer alteração na estrutura, deve-se avaliar o tamanho do impacto, tanto interno quanto externo, ou seja, descobrir todas as views, triggers, procedures, constraints e sistemas que serão afetados com a mudança.
  2. Definição do período de transição: após fazer a avaliação de impacto, será possível definir com mais precisão o período de transição. Entretanto, deve-se fazer um controle rigoroso para garantir que os prazos estabelecidos serão seguidos.
  3. Identificação de possíveis triggers circulares: em quase todas as refatorações que alteram a estrutura do modelo, é comum o uso de triggers para controlar o sincronismo dos dados. Se houver duas ou mais triggers, uma disparando contra a outra, e se não houver um controle rigoroso, elas irão entrar num loop infinito comprometendo o desempenho banco de dados.
  4. Criação e execução de testes: antes de alterar o modelo é preciso garantir que ele está atualmente funcionando. Crie testes automatizados para facilitar a identificação e correção dos impactos causados pelas refatorações.
  5. Executar a refatoração: crie os scripts necessários para aplicar a refatoração e adicione os comentários nas colunas, tabelas e triggers que serão removidas após o período de transição.
  6. Execute os testes: antes de anunciar as refatorações realizadas, é preciso garantir que nada parou de funcionar. Para isto, execute os testes criados antes da refatoração e caso algum deles falhe, faça as correções necessárias.
  7. Alteração das dependências internas e externas: se for possível, faça as alterações necessárias nos objetos internos (triggers, procedures, etc.) e externos (sistemas, outros bancos de dados, etc.) em paralelo. Caso contrário, prefira iniciar as alterações pelos objetos internos, já que estão mais próximos do seu domínio.

Estrutural

Conforme citado no artigo anterior, esta categoria engloba mudanças em estruturas de tabelas e/ou views, como:
  1. Renomear colunas, tabelas, views, functions e procedures.
  2. Introduzir colunas calculadas.
  3. Introduzir surrogates key.
  4. Mesclar colunas e tabelas.
  5. Mesclar tabelas.
  6. Mover colunas, tabelas, views, functions e procedures.
  7. Dividir tabelas com muitas colunas.
  8. Substituir associações um-para-muitos (1-N) por tabelas associativas.

Exemplo

Considere a tabela “Customer” que contém uma coluna chamada “Fname” que precisará ser renomeada para “FirstName”. Cria-se a nova coluna “FirstName”, mas não se remove de imediato a  coluna “Fname”, apenas adiciona-se um comentário na coluna antiga informando sua data da remoção. Em seguida, cria-se uma procedure para sincronizar os dados entre as duas colunas, já que elas existirão simultaneamente durante o período de transição.
ALTER TABLE Customer ADD FirstName VARCHAR(40);
COMMENT ON Customer.FirstName 'Renaming of FName column, finaldate = November 14 2007';
COMMENT ON Customer.FName 'Renamed to FirstName,
dropdate = November 14 2007';
UPDATE Customer SET FirstName = FName;
CREATE OR REPLACE TRIGGER SynchronizeFirstName
BEFORE INSERT OR UPDATE
ON Customer
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
DECLARE
BEGIN
IF INSERTING THEN
IF :NEW.FirstName IS NULL THEN
:NEW.FirstName := :NEW.FName;
END IF;
IF :NEW.Fname IS NULL THEN
:NEW.FName := :NEW.FirstName;
END IF;
END IF;
IF UPDATING THEN
IF NOT(:NEW.FirstName=:OLD.FirstName) THEN
:NEW.FName:=:NEW.FirstName;
END IF;
IF NOT(:NEW.FName=:OLD.FName) THEN
:NEW.FirstName:=:NEW.FName;
END IF;
END IF;
END;
/
On Nov 30 2007
DROP TRIGGER SynchronizeFirstName;
ALTER TABLE Customer DROP COLUMN FName;

Qualidade de dados

As implementações feitas nesta categoria visam melhorar a qualidade das informações contidas no banco de dados. São elas:
  1. Adicionar uma tabela de consulta.
  2. Aplicar códigos padrões.
  3. Aplicar tipos padrões.
  4. Adicionar ou remover chaves estrangeiras.
  5. Adicionar ou remover valores padrões em colunas.
  6. Mover dados de uma tabela para outra.

Considere a tabela “Address” que contém uma coluna chamada “State”. Nesta coluna são armazenadas as siglas dos estados com apenas dois caracteres. Neste modelo é possível apontar pelo menos dois problemas: o primeiro é a repetição desnecessária de dados, e a segunda é a falta de uma definição do significado de cada sigla.

Para resolver este problema pode-se adicionar uma tabela de consulta, também conhecida como Lookup Table ou Core Table. Nesta tabela, a coluna “State” vira chave primária, permitindo assim a criação de uma chave estrangeira na tabela “Address” para garantir a integridade. Além disto, deve ser adicionada uma nova coluna que irá qualificar o registro.

Outra vantagem com a aplicação desta refatoração é a possibilidade de fornecer aos sistemas externos uma lista de códigos nomeados para que não seja preciso usar enumeration.

Script:
CREATE TABLE State (
State CHAR(2) NOT NULL,
Name CHAR(50),
CONSTRAINT PKState
PRIMARY KEY (State)
);
ALTER TABLE Address ADD CONSTRAINT FK_Address_State
FOREIGN KEY (State) REFERENCES State;
INSERT INTO State (State)
SELECT DISTINCT UPPER(State) FROM Address;
UPDATE Address SET State = 'TX' WHERE UPPER(State) ='TX';
UPDATE State SET Name = 'Florida' WHERE State='FL';
UPDATE State SET Name = 'Illinois' WHERE State='IL';
UPDATE State SET Name = 'California' WHERE State='CA';

Integridade referencial

As implementações feitas nesta categoria visam garantir a integridade dos dados, são elas:
1. Adicionar ou remover chave estrangeira.
2. Adicionar trigger para coluna calculada.
3. Adicionar exclusão em cascata.
4. Adicionar trigger para logar as alterações.

Considere as tabelas “Account” e “AccountStatus”. Elas estão interligadas através da coluna “StatusCode”. No entanto, não existe uma referência que restrinja este relacionamento, ou seja, podem ser inseridos registros na tabela “Account” mesmo sem existirem na tabela “AccountStatus”. Para resolver este problema, deve-se adicionar uma chave estrangeira na tabela ”Account” apontando para “AccountStatus”.

Existem dois tipos de chave estrangeira: a que a checagem é realizada no momento da inserção do registro, e a que faz a checagem apenas no final da transação (no commit). A primeira é a padrão da maioria dos bancos de dados, mas diminui o desempenho da transação. A segunda é uma boa opção quando os registros são inseridos e alterados na mesma transação e o que importa é apenas o resultado final, mas se não for usada com cautela pode desperdiçar processamento do banco de dados.

Script para criar a chave estrangeira com verificação imediata:

ALTER TABLE Account
ADD CONSTRAINT FK_Account_AccountStatus
FOREIGN KEY (StatusCode)
REFERENCES AccountStatus;
Script para criar a chave estrangeira com verificação no final da transação:
ALTER TABLE Account
ADD CONSTRAINT FK_Account_AccountStatus
FOREIGN KEY (StatusCode)
REFERENCES AccountStatus
INITIALLY DEFERRED;

Arquitetura

As implementações feitas nesta categoria visam melhorar de forma global as interações entre os programas externos e o banco de dados. São elas:
  1. Adicionar métodos CRUD (procedures para inserção, consulta, atualização e exclusão).
  2. Adicionar encapsulamento à tabela através de views.
  3. Adicionar métodos de cálculos.
  4. Adicionar tabelas apenas para leituras.
  5. Migrar métodos dos sistemas externos para o banco de dados.
Algumas empresas preferem encapsular o acesso ao banco de dados fornecendo apenas métodos de inclusão, exclusão, atualização e consulta, através de procedures. Desta forma o banco de dados fica totalmente desacoplado das aplicações. Além disto, é possível implementar controle de acesso baseado na política de segurança.
A desvantagem neste tipo de modelo é o grande  número de objetos que se adiciona no banco de dados, exigindo assim, um controle rigoroso para evitar duplicações desnecessárias. Por outro lado, facilita quando uma nova refatoração for aplicada, pois sem a dependência dos sistemas externos, o período de transição é bem curto, e às vezes, até desnecessário.

Ao decidir usar métodos CRUD, leve em consideração os seguintes pontos:

  • Quantidade de sistemas com acesso ao banco de dados.
  • Regras de negócios que são repetidas em todos os sistemas e poderiam ser generalizadas no banco de dados.
  • Necessidade de implementação de políticas de acesso aos dados.
  • Familiarização da equipe de desenvolvimento com a linguagem usada pelo banco de dados.
Script de exemplo usando banco de dados Oracle:
CREATE OR REPLACE PACKAGE CustomerCRUD AS
TYPE customerType IS REF CURSOR RETURN
Customer%ROWTYPE;
PROCEDURE ReadCustomer
(readCustomerId IN NUMBER,customers OUT
customerType);
PROCEDURE CreateCustomer(....);
PROCEDURE UpdateCustomer(....);
PROCEDURE DeleteCustomer(....);
END CustomerCRUD;
/
CREATE OR REPLACE PACKAGE BODY CustomerCRUD AS
PROCEDURE ReadCustomer
(readCustomerId IN NUMBER,customerReturn OUT
customerType) IS
BEGIN
OPEN refCustomer FOR
SELECT * FROM Customer WHERE CustomerID =
readCustomerId;
END ReadCustomer;
END CustomerCRUD;
/

Conclusão

Os exemplos aqui apresentados mostram como é seguro e fácil evoluir um modelo de banco de dados utilizando a técnica de refatoração, e deixa claro também, que o gerenciamento destas mudanças é extremamente importante para o sucesso do projeto.

É provável que os desenvolvedores tenham um pouco de dificuldade no começo, caso nunca tenha tenham programado na linguagem do banco de dados, mas com um pouco de estudo e prática, será possível evoluir. Não veja estas dificuldades como uma barreira, mas sim como uma oportunidade para dividir o conhecimento, que às vezes, está concentrado apenas nos profissionais de banco de dados.

Evolua. Boa sorte!

Referências

  1. Ambler, Scott W., Pramod J. Sadalage (2006). Refactoring Databases: Evolutionary Databases Design. New York: Addison Wesley Professional.
  2. Ambler, Scott W., Pramod J. Sadalage (2006). Refactoring Databases: The Process.
Autor

André Faria Gomes é executivo, empreendedor, investidor, mentor, gerente, escritor, palestrante, podcaster e agilista. Atualmente, é CEO da Bluesoft em São Paulo, investidor e membro do conselho da Wow e mentor da Liga Ventures.

1 Comentário

Deixe aqui o seu comentário