Vitor Pamplona

Innovation on Vision: Imaging , Enhancement and Simulation

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

Showing 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

Parabéns cara, o tutorial ta mt bom!!


agora me explica uma coisa...

Como posso fazer um update num objeto?

Grato,

David Bentolila

- - David Bentolila

- - Posted in Sep 26, 2010 by 189.82.192.193

Ola vitor,
É possivel traçar um paralelo quanto ao consumo de energia usando mais memória ram e menos banco de dados / hd?
Abraço



Saving Data Center Power by Reducing HDD Spin Speed
Thursday, August 18, 2011 · Posted by Eran Tal at 20:40 PM

Many data centers sit on a lot of “ cold storage ” — servers containing terabytes of user data that must be retained but is rarely accessed, because users no longer need that data. While the servers are considered cold because they are rarely utilized, their hard drives are usually spinning at full speed although they are not serving data. The drives must keep rotating in case a user request actually requires retrieving data from disk, as spinning up a disk from sleep can take up to 30 seconds. In RAID configurations this time can be even longer if the HDDs in the RAID volume are staggered in their spin up to protect the power supply. Obviously, these latencies would translate into unacceptable wait times for a user who wishes to view a standard resolution photo or a spreadsheet.

Reducing HDD RPM by half would save roughly 3-5W per HDD. Data centers today can have up to tens and even hundreds of thousands of cold drives, so the power savings impact at the data center level can be quite significant, on the order of hundreds of kilowatts, maybe even a megawatt. The reduced HDD bandwidth due to lower RPM would likely still be more than sufficient for most cold use cases, as a data rate of several (perhaps several dozen) MBs should still be possible. In most cases a user is requesting less than a few MBs of data, meaning that they will likely not notice the added service time for their request due to the reduced speed HDDs. What is critical is that the latency response time of the HDD isn ’ t higher than 100 ms in order to not degrade the user experience.


HDDs however aren ’ t “ born ” cold; they progress into that state. In early stages, when user data is constantly being uploaded, or when data is recovered to the system due to a failure on a different machine, high disk bandwidth is a valuable asset. However, when a system ’ s data turns cold, there is no value to the high bandwidth. But copying over the data to a low bandwidth system requires too much overhead and would be slow (since the target is low bandwidth), and as a result isn ’ t a standard mode of operation for most providers. Therefore, having HDDs that can operate in either full speed (7200RPM) or reduced speed (say, 3600RPM), with a toggle to control the setting, could be useful. The transition between these states can be long (like 15 seconds), as this would likely be a one-time event, triggered by an entity capable of determining that the box is no longer hot.

The following table summarizes the proposed specification for a 3TB or larger SATA enterprise 7200 RPM HDD at normal and reduced speeds.

Item Value
Normal Speed 7200 RPM
Reduced Speed 3600 RPM
State Transition (triggered by an OS command) 15 seconds
Normal Idle Power 7W
Reduced Speed Idle Power 3W
Normal Bandwidth ~ 100 MB / s
Reduced Speed Bandwidth > 10 MB / s
Normal Latency ~ 10 ms
Reduced Speed Latency < 100 ms


What are your thoughts? Does your storage infrastructure experience similar behavior and would it be capable of realizing serious power savings provided the HDDs had this feature?

- - trainsppotting

- - Posted in Sep 14, 2011 by 189.69.147.154

Pelo pouco que utilizei deste framework, percebi que as Transações foram implementadas no padrão Command, Isto gera uma quantidade enorme de Classes mas com alta coesão correto?

é possivel Aplicar outros padrões na utilização do Prevayler? tipo, o AbstractFactory ou Factory Method, ou os 2 (DAO)??



- - Rafael William

- - Posted in Nov 17, 2011 by 189.26.123.104

Outra questão.

No lugar da classe ListaPessoa não poderiamos criar uma classe Generica, ou na verdade uma classe para armazanar as Entidades em geral em um List e posterirmente fazer consultas utilizando Reflection, ou uma cadeia de if's com instanceof ou algo do tipo?

- - Rafael William

- - Posted in Nov 17, 2011 by 201.89.0.102

Vitor, digamos que eu use ao inves de uma String, um tempo / data especifico de inicio para contagem regressiva, data de lançamento do prevayler.... e por algum motivo falte energia, inclusive no relogio do sistema e eu precise ' acordar ' a app para retornar atualizando a contagem regressiva a partir da data referencia (inicio), dai basta que ela retorne a data do sistema, desconte o tempo decorrido e reinicie o cronometro.

Minha duvida é: como persistir elementos datas e como poderia usar referencias e saber exatamente qual a correta: do sistema (relogio interno), da rede ou posso implementar (um relogio interno) dentro da app ' independentemente ' da imprecisão que possa ocorrer no relogio interno ou ausencia de rede?


long tempoInicio_Do_Prevayler = System.currentTimeMillis ();

depois para achar o tempo passado apenas subtraia do tempo de inicio

long tempoFinal = System.currentTimeMillis () - tempoInicioDoPrevayler; / / tempo em milisegundos

ou

long tempoFinal = (System.currentTimeMillis () - tempoInicio) / 1000; / / tempo em segundos

valew


- - ze sobrinho

- - Posted in Jun 3, 2013 by 200.205.164.130

Add New Comment

Your Name:


Write the code showed above on the text below.