Evoluindo o banco de dados com o liquibase


 

O que é

 

O liquibase é uma ferramenta concebida pra resolver O Problema do Versionamento do Banco de Dados (estou assumindo que vc leu esse post, antes deste!).

A idéia é dar uma melhorada no processo descrito no post anterior, de modo acabar com o “patch hell” desenhado lá.

Então com o liquibase é assim:

  1. Um patch – que na nomenclatura do liquibase se chama changeset – é uma transformação aplicada num banco de dados (pode ser um CREATE TABLE, um ADD COLUMN, um INSERT, um UPDATE, etc)
  2. Changesets são escritos em XML, numa notação específica do liquibase. O liquibase sabe traduzir esse XML pro SQL de um monte de SGDBs (pra casos mais complexos, vc sempre pode usar a tag <sql></sql> do liquibase, pra fazer patches “nativos”)
  3. Um arquivo XML pode conter vários changesets. Um arquivo desses é o que o liquibase chama de changelog.
  4. Changelogs podem fazer “include” de outros changelogs.
  5. Changelogs não devem ser apagados. A medida que o sistema evolui, o numero de changelogs só aumenta.
  6. ChangeSets são identificados por uma chave única formada por três elementos: O path do changelog, o autor do patch, e um id.
  7. Idealmente, changesets devem ser reversíveis. O liquibase sabe fazer rollback para algumas operações (por exemplo, <createTable>). Para outras, é preciso dizer a ele como fazer o rollback.
  8. Ao aplicar um changelog no banco, os changesets são executados na ordem em que aparecem nos changelogs.
  9. O liquibase armazena numa tabela do banco a chave (path + autor + id) dos changesets que ele já rodou, assim, ele só aplica os changesets que ainda não rodaram.
  10. O liquibase armazena também um checksum dos patches executados, e checa na hora de aplicar se o checksum de patches previamente executados mudou. Nesse caso, o comportamento default é interromper a execução com um erro, mas é possível mudar esse comportamento por changeset.
  11. O liquibase pode “desaplicar” um changelog no banco: ou seja, aplicar o rollback dos changesets na ordem inversa.

 

A grande vantagem disso tudo é que fica fácil_rápido atualizar um banco que está com patch faltando. E essa facilidade_rapidez naturalmente faz com que os desenvolvedores atualizem seus ambientes com maior frequência.

É uma linha de comando: rodou = atualizou (e ele mostra a lista dos patches que ele executou. Ou então dá pau, com uma mensagem de erro tipo assim:

“Erro no changeset 2012-03-20_Apagando_coluna_bolinha.xml:joao:3 – Não deu pra apagar a coluna bolinha pq tem uma foreign key apontando pra ela.”

Esse erro aih ia aparecer na maquina do Zé e dos outros desenvolvedores que pegaram esse patch. É fantástico o efeito positivo que tem no processo de desenvolvimento quando as mensagens de erro tem o nome do responsável provável pelo problema. As coisas tendem a ficar muito menos tempo quebradas.

Resumo da ópera: a inclusão dessa ferramenta no processo de desenvolvimento faz com que os problemas de migração de DB sejam detectados e corrigidos muito mais rápido, antes que eles tenham a chance de sair impactando em outras coisas e causando problemas mais sérios. É exatamente aqui que o prejuízo do patch hell é eliminado.

Bom, isso já dá uma bela idéia do que é o liquibase, e o que dá pra fazer com ele né?
Se vc é como eu, já deve estar pensando nas possibilidades de melhoria que esse negócio pode trazer pra sua empresa, ou projeto🙂

 

Como que usa

 

Vamos logo pra um exemplo:

Isto aqui é um changelog:

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">
 
	<changeSet id="1" author="bob">
		<createTable tableName="department">
			<column name="id" type="int">
				<constraints primaryKey="true" nullable="false"/>
			</column>
			<column name="name" type="varchar(50)">
				<constraints nullable="false"/>
			</column>
			<column name="active" type="boolean" defaultValueBoolean="true"/>
		</createTable>
	</changeSet>
 
</databaseChangeLog>

No site do liquibase tem o manual que detalha como fazer changelogs, com vários exemplos.
Vou cobrir só alguns casos aqui. Depois vc vai precisar ver o manual pra se aprofundar.

Esse changeset acima faz um <createTable>. Se vc for lá no manual dele, no final da página diz:
“Automatic Rollback Support: YES”.
Fantástico. Ele sabe que o contrário de <createTable> é <dropTable>.

Já o <dropTable> não:
“Automatic Rollback Support: NO”.

Isso significa que, pra um changeset com <dropTable>, vc precisa dizer como faz o rollback dele.
Isto é, se vc estiver interessado na possibilidade de fazer um “liquibase-rollback” um dia.
Se vc ainda está indeciso se precisa disso ou não, aqui vai uma dica: shit happens!

Anyway, um changeset com instrução pra rollback seria assim:

<changeSet id="2" author="ze">
	<dropTable tableName="pessoa"/>
	<rollback>
		<createTable tableName="pessoa">
			<column name="id" type="int">
				<constraints primaryKey="true" nullable="false"/>
			</column>
			<column name="nome" type="varchar(50)">
				<constraints nullable="false"/>
			</column>
		</createTable>
	</rollback>
</changeSet>

Os dois changesets vistos acima tem só uma instrução cada.
Isso não precisa ser o caso. Vc pode colocar mais de uma instrução no mesmo changeset.
Mas eu recomendo que vc não o faça, por dois motivos:

1) Se vc misturar no mesmo changeset, instruções do tipo Autorollback=YES com Autorollback=NO, o “Autorollback do changeset” é NO, portanto vc precisa fazer o <rollback> do changeset inteiro.

2) O liquibase interpreta o changeset como um a transformação atômica (ou seja: tudo ou nada), e ele tenta forçar que isso seja verdade usando uma transação do banco. Só que é o seguinte amigo: não é todo banco que suporta rollback de CREATE TABLE por exemplo. E aí se o seu patch multi-instrução quebrar no meio – vc ferrou com a atomicidade: o liquibase vai considerar o patch não rodado (sendo que ele foi parcialmente rodado), e da próxima vez vai tentar rodar de novo – tudo de novo. Tá vendo a zica que pode dar né?

Vai por mim: Fica com uma instrução por changeset.
Até dá pra abrir algumas exceções, por exemplo, pra changesets com um monte de INSERTs.
Use o bom senso, e tente estabelecer um consenso entre os membros da equipe que vão precisar criar patches.

A próxima coisa útil pra saber é como fazer include de changelogs. Faz assim:

<?xml version="1.0" encoding="UTF-8"?>
 <databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9
  http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd">

	<include file="versao-1.0/master.xml"/>
	<include file="versao-2.0/master.xml"/>

</databaseChangeLog>

Normalmente vc vai querer dividir seus patches em pastas, e aí ajuda se vc fizer puder fazer o include usando o caminho relativo dos changelogs. Faça assim:

<?xml version="1.0" encoding="UTF-8"?>
 <databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9
  http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd">

	<include file="2012-02-01_CriaTabelaPessoa.xml" relativeToChangelogFile="true"/>
	<include file="2012-02-15_CriaTabelaTelefone.xml" relativeToChangelogFile="true"/>
	<!-- O valor default de relativeToChangelogFile é false! -->

</databaseChangeLog>

Bom, uma vez que vc tem um changelog (que provavelmente vai incluir outros), vc manda o liquibase aplicar o changelog, via linha de comando.

É só baixar do site e descompactar. Tem lá o script “liquibase” pra linux e o “liquibase.bat” pra windows.
Como ele é feito em Java, vc vai precisar de uma JVM instalada, e também do driver jdbc do seu banco.

A sintaxe da linha de comando (pra linux, neste exemplo) é assim:

liquibase --driver=com.mysql.jdbc.Driver \
     --classpath=/path/to/classes \
     --changeLogFile=com/example/db.changelog.xml \
     --url="jdbc:mysql://localhost/example" \
     --username=user \
     --password=asdf \
     migrate

(Substitua esse “/path/to/classes” pelo jar do driver jdbc do seu banco.)

Bom, o que foi dito até aqui deve ser o suficiente pra vc começar a usar o liquibase de maneira útil no seu projeto.

 

Dicas extras

 

É importante dar algumas dicas “extras”, que poderão te economizar algum tempo no futuro.

PreConditions:

Voce pode criar pré-condições para changesets.

Seja pra fazer validações de “sanidade” antes de aplicar um changeset, ou pra criar um changeset condicional.

Tipos de colunas:

Ao especificar tipos de colunas para tabelas, vc pode usar as contantes da classe java.sql.Types, desse jeito:

<createTable tableName="pessoa">
	<column name="id" type="java.sql.Types.INTEGER">
		<constraints primaryKey="true" nullable="false"/>
	</column>
</createTable>

O liquibase vai converter pro tipo equivalente no seu banco. Isso serve pra deixar o código do seus changesets mais independente do SGBD.

Comentários:

Você pode colocar comentários nos changesets. Esses comentários são gravados na tabela databasechangelog (que é onde o liquibase grava quais patches ele já rodou).

<changeSet id="1" author="ze">
	<!-- Esse comentario vai pra tabela databasechangelog -->
	<comment>Criando a tabela pessoa</comment>
	<createTable tableName="pessoa">
		<column name="id" type="int">
			<constraints primaryKey="true" nullable="false"/>
		</column>
		<column name="nome" type="varchar(50)">
			<constraints nullable="false"/>
		</column>
	</createTable>
</changeSet>

Recomendo fortemente que todos os changesets tenham comentários.
Desse modo fica fácil ver a história do banco fazendo um select na tabela databasechangelog.

Patches Nativos:

Às vezes vc precisa criar um patch “nativo”, feito em SQL mesmo.

Pra isso tem o <sql>.

<changeSet author='jsmith' id='1' runAlways='true'>
	<!-- Um treco desse, só em SQL direto -->
	<sql splitStatements="false">
		DECLARE
			cursor c_newviews is
				select table_name
				from user_tables
				where table_name like 'DATABASECHANGELOG%'
				AND table_name||'_VW' not in
				(select view_name from user_views);
		BEGIN
			FOR r_newviews in c_newviews LOOP
				EXECUTE IMMEDIATE
				'CREATE VIEW ' || r_newviews.table_name || '_VW ' ||
				'AS SELECT * FROM ' || r_newviews.table_name;
			END LOOP;
		END;
	</sql>
</changeSet>

Note o spliStatements=false.
O default é true, e faz com que o liquibase faça um split do comando usando ‘;’ (o que obviamente não ia dar muito certo no comando acima.)

Documentação:

O site do liquibase é muito bem documentado. Tudo que vc precisa tá lá, com exemplo de tudo.

Aliás, deu uma melhorada de uns tempos pra cá. Agora tem até vídeo explicando o código fonte! Melzinho na chupeta né, fala sério!

Não mova nem renomeie!

Cuidado com o seguinte fato: O caminho do arquivo xml (changelog) faz parte do id dos chansets que tem dentro dele. Isso significa que se vc renomear ou mover esse arquivo, vc está alterando esse id (que o liquibase usa pra saber se um patch já foi aplicado ou não). Se vc fizer isso com um arquivo que já tenha rodado em algum ambiente, no próximo liquibase-update ele vai tentar ser aplicado de novo, o que provavelmente vai dar errado!

Patch rodado é “imexível”

A partir do momento que vc comita um changelog no controle de versão, vc teoricamente não tem mais controle de onde e quando esse patch vai ser aplicado. Ou seja, 2 minutos depois do seu commit, esse patch já pode ter sido aplicado no BD local de outro desenvolvedor.

Se depois disso vc por acaso descobrir que tem um erro no seu patch, a pior coisa que vc pode fazer é modificar o patch e comitar de novo. Se vc leu com atenção, deve lembrar que o liquibase gera um checksum dos patches já aplicados e dá pau se tiver diferente. Ou seja, vai dar pau no ambiente dos outros: sacanagem né.

Então, lembre-se dessas duas regras:
1. Teste seu patch no seu banco local antes de comitar. Outro nome pra essa regra é “vergonha na cara”😛.
2. Se vc realmente só percebeu um erro no seu patch depois de comitar, crie outro patch pra corrigir.

A não ser que…

Truque sujo: mudar o id do changeset

Então… na verdade a regra acima pode ser “entortada” dependendo do caso, desde que isso não vá atrapalhar ninguém…

Veja esse exemplo – o Zé está criando um patch pra mudar o servidor de SMTP que a aplicação usa pra mandar emails:

<?xml version="1.0" encoding="UTF-8"?>
<changeSet id="3" author="ze">
	<update tableName="system_configuration">
		<column name="cfg_value" value="smtp.googel.com"/>
		<where>cfg_name='smtp.hostname'</where>
	</update>
</changeSet>

svn commit… OK!

Aih o Zé se toca:

PQP, digitei errado essa %$#%@$# desse endereço %#@$#@@!
Ahhh, já sei, vou trocar o id do patch, o liquibase vai pensar que é um patch novo e vai reaplicar.
Num BD que já rodou, vai rodar de novo… blz. Num BD que não rodou ainda, vai funcionar tb… blz!

<?xml version="1.0" encoding="UTF-8"?>
<changeSet id="3_fixed" author="ze">
	<update tableName="system_configuration">
		<column name="cfg_value" value="smtp.google.com"/>
		<where>cfg_name='smtp.hostname'</where>
	</update>
</changeSet>

Entendeu? Mas pensa bem antes de usar esse truque sujo aih blz?

Atalho de teclado é mão na roda:

Padroniza aí no ambiente de desenvolvimento da equipe: CTRL+ALT+L+U (ou qualquer outra combinação da sua escolha) starta um .bat ou .sh que: 1) baixa a versao mais nova dos patches de controle de versão; e 2) Aplica o liquibase. Esse é o tipo de coisa que vira uma “mão na roda” depois:

“Ih, apareceu um erro reclamando que não existe a coluna cor_favorita_id, peraí, CTRL+ALT+L+U… 10 segundos depois … OK, onde é que eu tava mesmo? Ah sim, testando a tela do cadastro do animal de estimação”

Bugs!

Eu mexi bastante com o liquibase na versão 1.9.5 (a versao estável é a 2.0.3, lançada em outubro de 2011).
Essa versão 1.9.5 tinha uns bugzinhos, mas nada que impeça de usar. Essa versão nova eu não olhei ainda.
De qq jeito o código é aberto, vc pode mexer e até contribuir pro projeto (e é fácil, a arquitetura dele é bem feitinha).

 

Agora TE VIRA NEGO!

 

É isso, agora baixa lá, faz uns testinhos, mostra pra galera da sua equipe.
Tente chegar numa estrutura legal de como organizar os changelogs no controle de versão (aliás no site tem dicas até pra isso).
E se o seu projeto é grande e vc JÁ está vivendo num “patch hell”, pelo amor de Deus, pare de ter prejuízo e comece a usar esse negócio!

Se quiser volte aqui e comente sua experiência, ou até mesmo dúvidas. Tamos aí :-

Bubble sorte!
[ ]’s
O Lâmpada

Não perca nos próximos posts:

  • Configurando o liquibase num projeto pequeno usando maven
  • Estendendo a estrutura anterior para um projeto grande e modular
  • O que fazer quando houver dependências cíclicas entre os módulos no nível do BD
  • Incluindo testes para os patches liquibase no processo de integração contínua
  • Use o liquibase para reduzir drasticamente o tempo de deploy de novas versões em um ambiente de produção

7 comentários em “Evoluindo o banco de dados com o liquibase

  1. Gostei do artigo, qual seria a forma correta de levar esses scripts para aplicar em produção pelo DBA do cliente?

  2. Estou precisando de fazer a ferramenta Liquibase funcionar, para realizar alguns testes. Porém não localizo nehhum tutorial completo com os passos para instalação. Não estou conseguindo fazer funcionar de forma nenhuma. Agradeço se puder me ajudar.

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s