Vitor Pamplona

Tutorial Prevayler 2

Este artigo é uma recompilação de três tutoriais que fiz em 2004, quando o Prevayler 1 ainda era a release default.

Todos nós estamos pra lá de acostumados com os tradicionais bancos de dados relacionais. Em java, trabalhar com esse tipo de armazenamento de dados não é tão fácil. Se você não pensar em uma boa arquitetura para a sua aplicação, o seu código fonte fica horrível, principalmente se você é um daqueles que adora digitar comandos SQL no meio do código Java.

Mas, vamos lá! Quais são as principais diferenças entre os bancos de dados relacionais e o Prevayler? Simples, o Prevayler não é um banco de dados, portanto, não tem as características de um, como: controle de acesso, compartilhamento de dados, integridade, interface via SQL, etc. O Prevayler é um simples gravador e recuperador de objetos. Você manda gravar e ele grava, é simples.

Muitas pessoas tem medo do Prevayler em aplicações grandes pela falta da SQL. Isso mesmo, se você vai usar o Prevayler, você não poderá executar SQLs em seu sistema. Ao invés disso, quem faz a lógica da busca é o próprio programador, ou seja, o programador faz tudo. Se insistir, você pode usar o JoSQL para fazer consultas na base do Prevayler.  

Vamos a um breve exemplo. Digamos que você queira fazer um sistema que salve somente o nome das pessoas. Como você é um programador que gosta de um desenvolvimento caprichado, você irá criar uma classe Pessoa, contendo apenas o atributo nome. Como essa abaixo:

public class Pessoa {       
private String nome;
public Pessoa(String nome) {
this.nome = nome;
}

public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
}  

Além disso, você definirá de uma classe para armazenar as pessoas que você já anotou.

import java.util.ArrayList;
import java.util.List;

public class ListaPessoas {
private List<Pessoa> listaPessoas = new ArrayList<Pessoa>();

public void add(Pessoa pes) {
listaPessoas.add(pes);
}
public Pessoa get(int i) {
return listaPessoas.get(i);
}
public int size() {
return listaPessoas.size();
}
}

Pronto. Mas e agora, como iremos armazenar isso? Já pensou em uma tabela de banco? Ok, pode ser feito também, mas você reparou no trabalho que isso vai lhe custar? Comprar ou baixar um banco de dados (lembre-se da licença dupla) e instalá-lo e criar a tabela. Se pensarmos no Prevayler, como seria? Muito mais simples. Você só precisaria criar uma classe para o método add da sua lista de pessoas. Vejamos.

Antes de mais nada crie um projeto Java na sua IDE preferida. Baixe o jar mais atual do repositório do Prevayler e coloque-o em seu classpath.  

Primeiro passo no uso do Prevayler é implementar Serializable nas classes que serão persistidas: Pessoa e Lista Pessoas. Permita-me explicar o que é Serialização. Quando você tem um objeto instanciado, ele pode não estar armazenado sequencialmente na memória. Por esse motivo existe a serialização, que nada mais é do que colocar os valores que o objeto está utilizando juntamente com suas propriedades de uma forma que fique seqüencial, um único stream de dados. Tornando um objeto, estamos atribuindo essa qualidade a ele, e dando privilégios para que o mesmo possa ser gravado em disco ou enviado por rede (Serial - Bit a Bit).

Ao criar uma classe serializável deve-se definir a constante serialVersionUID, que controlará a versão da classe. Este número nunca deve mudar. Se ele mudar, ou se o programador não definí-lo e a classe for re-compilada, o java não importará os dados da classe salvos, pois não saberá tratar a mudança do versionamento da classe.  

A ListaPessoas ficará assim:  

...
public class ListaPessoas implements Serializable {
private static final long serialVersionUID = 1L;
private List<Pessoa> listaPessoas = new ArrayList<Pessoa>();
...

E a Pessoa:

...
public class Pessoa implements Serializable {
private static final long serialVersionUID = 2L;
private String nome;
....

Não se esqueça de importar Serializable em ambas as classes.

Em seguida, vamos criar a nossa transação, a AdicionaPessoa, que também será serializada e irá adicionar uma pessoa em nossa base:  

import java.io.Serializable;
import java.util.Date;

import org.prevayler.Transaction;

public class AdicionaPessoa implements Transaction, Serializable {
private static final long serialVersionUID = 3L;
private String nome;

public AdicionaPessoa(String nome) {
this.nome = nome;
}

public void executeOn(Object businessSystem, Date date) {
((ListaPessoas) businessSystem).add(new Pessoa(nome));
}
}

O tudo que estiver dentro do método executeOn será uma transação ACID do Prevayler. Desta forma, todo que está dentro desta função será executado uma única vez e por completo. Caso haja uma excessão dentro deste método, o Prevayler retorna a versão da base que ele salvou automaticamente ao chamar o método execute. Este método também é sincronizado, ou seja, o Prevayler só permite uma execução de transação por vez.  

Para testar, crie uma classe Main como esta:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.prevayler.Prevayler;
import org.prevayler.PrevaylerFactory;

public class Main {

static Prevayler prevayler;

public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("Iniciando Prevayler...");

PrevaylerFactory factory = new PrevaylerFactory();
factory.configurePrevalenceDirectory("BASE");
factory.configurePrevalentSystem(new ListaPessoas());
prevayler = factory.create();

System.out.print("Digite nome da pessoa ou FIM para sair: ");
String nome = lerTeclado();
while (!nome.equals("FIM")) {
try {
prevayler.execute(new AdicionaPessoa(nome));
} catch (Exception e1) {
e1.printStackTrace();
}
System.out.println("Pessoa armazenada.");
System.out.print("Digite o nome da pessoa ou FIM para sair: ");
nome = lerTeclado();
}

System.out.println("Imprimindo pessoas persistidas.");
ListaPessoas lista = ((ListaPessoas) prevayler.prevalentSystem());
for (int i = 0; i < lista.size(); i++) {
System.out.println(lista.get(i).getNome());
}
}

public static String lerTeclado() {
BufferedReader reader = new BufferedReader(new InputStreamReader(
System.in));
try {
return reader.readLine();
} catch (IOException e1) {
return "FIM";
}
}
}

As primeiras linhas do programa criam o mecanismo de persistência do Prevayler com a base de dados apontando para um diretório chamado BASE. Note que é informado a classe que controla todo o banco de dados. E esta classe é o programador quem define. Em seguida o programa lê um conjunto de nomes e os persiste no banco. Por último, imprime todos os nomes cadastrados.  

Repare que você pode fechar o programa a qualquer hora, em qualquer circunstância. Os dados ficarão armazenados corretamente. Se você não acredita, de um reset no micro bem na hora de ele salvar os dados. Se vc verificar a pasta BASE irá encontrar alguns arquivos. journal. Estes são os arquivos onde as transações, no nosso caso instâncias da AdicionaPessoa, são salvos. Por enquanto, o Prevayler está salvando só as transações e não o banco por completo. Ao iniciar, o Prevayler re-executa todas as transações e o seu banco estará no mesmo estado que vc o deixou na última execução.  

Este é um exemplo bem simples, lembre-se que se você for implementar algo profissionalmente com o prevayler, você deve conhecer alguns padrões de projeto. Eles serão necessários para que se faça uma boa programação e torne possível uma futura manutenção.

Snapshots

Apesar de prático, re-executar os logs é um processo lento. Para evitar, podemos tirar, a qualquer momento, um screenshot da base e salvá-lo em disco. Este processo se chama Snapshot. Quando um snapshot é feito, o prevayler não precisa mais executar   as transações anteriores ao Snapshot, diminuindo o tempo de carga. A classe abaixo é uma thread que faz snapshots da base através do método takeSnapshots de 6 em 6 horas.  

import java.io.IOException;

import org.prevayler.Prevayler;

public class SnapshotTimer extends Thread {
Prevayler prevayler;

public SnapshotTimer(Prevayler prevayler) {
this.prevayler = prevayler;
this.setDaemon(true);
}

public void run() {
super.run();

while (true) {
try {
Thread.sleep(1000 * 60 * 60 * 6); //6 horas
prevayler.takeSnapshot();
System.out.println("Snapshot disparado as " + new java.util.Date() + "...");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

Para utilizá-la, basta adicionar a linha abaixo na classe Main, após o método create da PrevaylerFactory:

      new SnapshotTimer(prevayler).start();  

Pronto. Você já pode testar. Note que existirá um arquivo. snapshot no diretório BASE do prevayler.  

Mudanças nas classes Serializáveis

Apesar de simples, o Prevayler exige que o desenvolvedor mantenha a compatibilidade das classes de dados. Na carga dos dados, o java irá verificar quais os atributos da classe ainda existem, e estes serão carregados. Os dados de atributos removidos ou renomeados são jogados fora. Os novos atributos serão inicializados com seus valores default. A ordem dos atributos não interessa, portanto você pode alterar a ordem que não haverá nenhum problema. A serialização salva e busca com o nome do atributo. Por isso mesmo que o nome não pode ser alterado.

Vc poderá encontrar mais informações sobre isso neste post. Mas recomendo fortemente que vc teste várias alterações nas classes serializáveis em vários contextos para identificar as mensagens de erro.  

Replicação  

O Prevayler 2 suporta replicação de base, instâncias do seu sistema poderão trabalhar em uma base única de maneira transparente. A replicação mantém todas as informações em todos os computadores com acesso (por isso o nome Replication), e todas as transações são executadas em todos os computadores. Tudo isso é transparente, basta configurar de maneira adequada.

Lembra-se da classe PrevaylerFactory? Observe o código de um servidor de Replication:

    PrevaylerFactory factory = new PrevaylerFactory();  
// Configura o objeto principal da persistência
factory.configurePrevalentSystem(new ListaPessoas());
// Configura o diretório para manter os dados em disco
factory.configurePrevalenceBase("Base");
// Informa que esta instância é um servidor de replicação
factory.configureReplicationServer(PrevaylerFactory.DEFAULT_REPLICATION_PORT);
// Cria o prevayler
Prevayler prevayler = factory.create();

E agora o código de um cliente:

    PrevaylerFactory factory = new PrevaylerFactory();  
// Configura o objeto principal da persistência
factory.configurePrevalentSystem(new ListaPessoas());
// Configura o diretório para manter os dados em disco
factory.configurePrevalenceBase("Base");
// Informa onde se encontra o servidor de replicação
factory.configureReplicationClient("192.168.0.200", PrevaylerFactory.DEFAULT_REPLICATION_PORT);
// Cria o prevayler
Prevayler prevayler = factory.create();

Agora basta criar uma forma de dizer, ao executar o Main, quem é cliente e quem é servidor, chamar os métodos certos e pronto!  

Posted in Dec 5, 2008 by Vitor Pamplona - Edit - History

Comments

Parabéns por essa atualização do artigo.



- - Edson Gonçalves

- - Posted in Dec 6, 2008 by 189.46.5.77

Muuuuito bom.

- - Gustavo Yu

- - Posted in Mar 4, 2009 by 200.146.0.254

Parabéns Vitor, muito bom!


- - Robson v Farias

- - Posted in Dec 4, 2009 by 201.77.94.28

Comment!

Your Name:


Write the code showed above on the text below.