Código no Banco X Código na Aplicação

Propaganda
: : www.mundoj.com.br : :
Erik Aceiro Antonio
([email protected],
[email protected],
http://
erikblogger.blogspot.com/): Graduado em Ciência da
Computação (Mackenzie). Mestre em Engenharia (Mackenzie)
com ênfase em Automação de Sistemas de Comunicações
Ópticas. Doutorando em Engenharia de Software pela UFSCar
sobre Atividades de VV&T. Possui as certificações SCJP 6.0 e
LPI 101. Professor universitário da Anhanguera de Rio Claro e
engenheiro de Software Master na VARITUS-TI. Tem experiência
na implantação de TDD (Test-Drive-Development), Refactory,
Padrões de Arquitetura.Web Services, JPA, EJB e J2ME.
Código
no Banco
X
Código na
Aplicação
Dez variáveis que influenciam na tomada de decisão de um projeto Orientado a Objetos.
Separação de Interesses, do termo
em inglês Separation of Concern
(SoC), esse é um conceito antigo e
amplamente usado no desenvolvimento
de projetos complexos e com requisitos
altos de escalabilidade, portabilidade
e com facilidade de manutenção. O
desenvolvimento em Java confundese, às vezes, com o uso de Stored
Procedures (SPs), portanto, qual é a
melhor opção, código no Banco de
Dados ou o Código na Aplicação. Este
artigo pretende discutir e apresentar
dez variáveis para conduzir a decisão
de se escolher manter o código no
banco de dados ou na aplicação.
56
egundo Martin Fowler, obter alta granularidade não é uma
tarefa fácil. A granularidade é um tipo de métrica empregada em orientação a objetos e outros tipos de paradigmas
que têm como característica medir o grau que uma parte do sistema
deve ser fatorado (ou dividido em outras partes), de forma que se
possa criar um componente que possa ser reaproveitado/reutilizado.
Existem dois tipos de granulariade conhecidas: granularidade grossa
e granularidade fina. A primeira relaciona um alto grau de acoplamento e um sistema pouco coeso e a segunda mede o inverso, um
sistema fracamente acoplado e fortemente coeso [1].
Outro conceito importante em Orientação a Objetos é o nível de
separação de interesses que se deseja alcançar. Separação de Interesses ou Separation of Concern (SoC) do inglês, é um conceito antigo
e amplamente usado em sistemas que tenham um certo grau de
complexidade. Provavelmente o primeiro a ter introduzido o termo
separação de interesses foi Dijkstra em 1974 no seu artigo “On the
role of scientific thought”, onde relaciona o termo com os inúmeros
aspectos e possibilidades existentes de pensamento. Em geral, é desejável que se tenha níveis de granularidade coerentes com o projeto
desenvolvido. Para se alcançar esses níveis deve-se realizar um "tradeoff" entre os princípios de orientação a objetos e as necessidades do
projeto, algumas variáveis que devem ser identificadas e controladas
como pontos de prova são: (1) desenvolvedores com conhecimento
na linguagem; (2) flexibilidade; (3) portabilidade; (4) desempenho;
(5) tempo de desenvolvimento; (6) capacidade de testar o sistema e
efetuar a depuração; (7) escalabilidade; (8) compreensão do código;
(9) duplicidade; (10) encapsulamento.
Esses dez princípios enumerados anteriormente estão diretamente
associados com dois fatores constantes ao desenvolvimento de software – Banco de Dados (BD) e Linguagem de Programação (LP).
Essas duas constantes levam a decisões de projetos importantes, um
deles é se a codificação da regra de negócio do sistema deve estar localizada no banco de dados de dados, na forma de Stored Procedure
(SP) ou codificada na aplicação.
O artigo primeiro trata da motivação quanto à separação de interesses, mostrando exemplos de código de banco de dados entrelaçado
com o código da aplicação, em seguida é apresentada uma discussão
sobre as dez variáveis de controle com foco em medidas de desempenho e manutenção, finalmente uma tabela relaciona o código com no
banco ou na aplicação que auxilia na tomada de decisões.
Separação de interesses
A separação de interesses é uma abordagem que inclui diversos
aspectos simultaneamente. Às vezes, tal separação não é perfeitamente possível, mas é ainda uma técnica disponível para o efetivo
ordenamento dos pensamentos. A seguir, são apresentadas três
formas de separação de interesses para uma aplicação simples de
persistência de dados. Essa separação pode ser encontrada em um
projeto orientado a objetos quando encapsulamos o comportamento de um objeto na forma de um método (veja a Listagem 1).
Listagem 1. Código em Java para recuperar o pedido atual.
class LogicaPedido{
Collection<Pedido> result;
//...
public Collection<Pedido> pedidoMesAtual(){
int clienteID = daocliente.buscaCliente(“eva”);
for(Pedido dataPedido: daopedido.buscaPedidoPeloID(clienteID))
if( dataPedido.pedidoAtual() )
result.add(dataPedido);
return result;
}
}
class DAOCliente{
public int busca_cliente(String nome) throws SQLException{
String sql=”SELECT * FROM cliente WHERE nome=?”;
//Rotina para usar o PreparedStatement
return id;
} }
class DAOPedido{
public Collection<Pedido> busca_pedido(int id) throws SQLException{
String sql=”SELECT * FROM pedido WHERE id=?”;
Collection<Pedido> pedidos;
//Rotina para usar o PreparedStatement
return pedidos;
} }
A listagem anterior mostra apenas o DAOCliente e DAOPedido.
Na listagem é possível observar claramente a separação de interesses entre a aplicação e o código SQL do banco de dados. A
separação de interesses foi obtida com auxílio do padrão de projeto DAO, nesse caso o padrão de projeto consegue separar com
alta granularidade os componentes do sistema responsáveis pela
camada de persistência, centralizando assim a responsabilidade
da lógica. Um modelo ideal para esse exemplo, seria a integração
dos padrões DAO e MVC [4,5,9].
Em contrapartida, uma segunda abordagem é apresentada na
Listagem 2.
Listagem 2. Código em Java para recuperar o pedido atual
com SQL.
class LogicaPedido{
Collection<Pedido> result;
//outras variaveis de instancias DAOCliente DAOPedido
public Collection<Pedido> pedidoMesAtual() throws SQLException{
int clienteID;
String nome;
PreparedStatemente st;
Collection<?> result;
String sql =”SELECT DISTINCT MONTH(ip.data) AS mes FROM itens_
pedido ip INNER JOIN pedido p ON ip.pedidoID = p.pedidoID INNER JOIN
cliente c ON p.clienteID = c.clienteID WHERE (c.nome = ?) AND(ip.produto
= ‘tablet’) GROUP BY ip.pedidoID, ip.data, c.nome HAVING (SUM(ip.preco)
> 5000)”;
//criar PreparedStatement
//...
ResultSet rs = st.executeQuery(sql);
while(rs.next()){
result.add(rs.getDate(“mes”).toString());
}
return result;
}
}
A Listagem 2 é um exemplo de código em Java que usa diretamente uma associação com a linguagem SQL. Embora o exemplo
SQL não seja complexo, essa listagem é um exemplo claro que
à medida que a aplicação vai necessitando de novos recursos e
consultas, o código SQL pode se tornar cada vez mais complicado
e de difícil manutenção. Outro exemplo é mostrado na Listagem
3, onde é usado para retornar um Pedido uma Stored Procedure
(SP) em Postgres/plpgsql. Essa SP usa um cursor para retornar um
conjunto de itens, esse cursor é aberto com um comando SQL
equivalente ao da Listagem 2, observe no entanto as diferenças
entre as formas de declaração entre as listagens anteriores.
Como se pode observar, da primeira listagem até a última (Listagem de 1 a 3), que o nível de abstração e semântica vai do maior
para um menor grau de abstração. A Listagem 3 não apresenta
um nível desejável de SoC e também viola condições e conceitos importantes de orientação a objetos como encapsulamento e
reutilização de código. Pode-se observar que essa listagem apresenta um alto nível de acoplamento e baixa coesão, pois a função
fn_retorna_pedido() não pode ser um componente e "plugada"
facilmente em outras partes do sistema (observe os métodos da
Listagem 1).
57
: : www.mundoj.com.br : :
Listagem 3. Código de uma Stored Procedure em Postgres/
plpgsql.
CREATE OR REPLACE FUNCTION fn_retorna_pedido(integer) RETURNS TEXT
AS
$$
DECLARE
ppedido
cursor
ALIAS FOR $1;
REFCURSOR;
col_data
exemplo.itens_pedido.data%TYPE;
result
TEXT;
As linguagens de referência no índice TIOBE (mês de referência junho de 2010), com aproveitamento acima de 50% são as
linguagens C/C++ e Java. É conveniente notar que segundo o
índice TIOBE, essas linguagens são as mais populares a partir dos
principais motores de buscas atuais. Nota-se uma mínima popularidade em relação à família de linguagens PL/SQL (3,94%) contra
(90,17%) da linguagem Java. Essa característica tem um impacto
direto na quantidade de desenvolvedores que tem conhecimento
sobre linguagem de banco de dados (SP), isso pode em certo sentido prejudicar o bom desenvolvimento do sistema, caso o sistema
seja desenvolvido com prazos críticos e com pessoal não treinado.
Portabilidade e desempenho
BEGIN
result:=’’;
OPEN cursor FOR SELECT DISTINCT MONTH(ip.data) AS mes
FROM itens_pedido ip INNER JOIN pedido p ON ip.pedidoID
= p.pedidoID
INNER JOIN cliente c ON p.clienteID = c.clienteID
WHERE (c.nome
= ppedido) AND (ip.produto = ‘IPhone’)
GROUP BY ip.pedidoID, ip.data, c.nome
HAVING (SUM(ip.preco) > 5000);
LOOP
FETCH cursor INTO col_data
EXIT WHEN NOT FOUND;
result:=result||col_data || ‘ \n ‘; /* || é uma concatenacao */
END LOOP;
RETURN result;
END;
$$
LANGUAGE ‘plpgsql’ VOLATILE
Desenvolvedores com conhecimento na
Linguagem
A figura 1 ilustra dez Linguagens de Programação (LPs) em função
da porcentagem/popularidade dos principais motores de buscas
da Internet (veja a figura 1).
Linguagem de Programação como PL/SQL são linguagens que
estão diretamente ligadas a banco de dados, como Oracle, mySQL, Postgres, SQL Server, Informix, Sybase, DB2, entre outros.
Segundo Martin Fowler, existe uma dependência direta entre um
sistema de banco de dados e a capacidade do sistema se tornar
mais flexível. O código totalmente implementado no banco de dados caracteriza por outro lado um alto desempenho se comparado
com as chamadas preparadas em Java. O desempenho do banco
de dados se comparado com rotinas alocadas no pool de conexão da linguagem Java e servidores de aplicação como o Apache
Tomcat e Glassfish podem, para consultas complexas, executar
dezenas de vezes mais rápidas.
Esse resultado, em alguns casos, é tomado como o primeiro na
decisão de projeto e arquitetura de um sistema, no entanto tal
prática deve ser claramente analisada. Como primeira comparação, considere uma implementação em linguagem C para uma
tarefa de troca de valores, esse tipo de operação é muito comum
para algoritmos de ordenação como o Bubble Sort. A Listagem 4
mostra um exemplo de uma chamada em Assembly (AT&T) para
realizar uma operação de troca.
Listagem 4. Código em Linguagem de Máquina integrado
com C para operações de troca.
void troca_lm(int a, int b){
int aTemp,bTemp;
asm (“movl %2, %%eax;”
“movl %3, %%ebx;”
“movl %%eax, %%edx;”
“movl %%ebx, %%eax;”
“movl %%edx, %%ebx;”
“movl %%eax, %0;”
“movl %%ebx, %1;”
:”=r”(aTemp),”=r”(bTemp) /* saida */
:”r”(a),”r”(b)
/* entrada */
:”%eax”, “%ebx”, “%edx” /* registradores usados */
);
Figura 1. Dez linguagens de programação (adaptado TIOBE –
http://www.tiobe.com/) [2].
58
}
A figura 2 ilustra o tempo de troca em linguagem C com a
integração da Linguagem de Montagem LM-AT&T (curva em
vermelho), o tempo em linguagem C puro sem o uso de LM
(curva em vermelho) e uma curva de Speedup (curva em azul).
Para mais informações sobre código em LM (ver links) [8].
resultados apresentados (figura 2) obteve-se valores de Speedup
de aproximadamente 1,3.
Como era de se esperar, observa-se uma ligeira melhora da linguagem de montagem sobre a linguagem C. Essa melhora deve-se
a capacidade da linguagem C acessar diretamente registradores
otimizando assim a aplicação. É importante notar que mesmo a
linguagem de montagem tendo um resultado melhor, o algoritmo
Figura 2. Número de troca em função do tempo para código em C e AT&T.
A curva de Speedup é definida como o tempo de execução de
uma linguagem serial sobre o tempo de execução de uma linguagem em paralelo. Essa medida geralmente é empregada em
sistemas paralelos e distribuídos e tem como objetivo investigar
o quanto uma linguagem é melhor em relação à outra. No livro
Introduction to Assembly Language Programmmin (ver livros) o
autor usa este tipo de medida como uma razão entre o tempo de
execução da linguagem C sobre o tempo de execução da linguagem de Montagem para mostrar o seu desempenho. Um valor de
Speedup maior que 1 indica que a Linguagem de Montagem (LM)
é melhor (executa em menor tempo) que a linguagem C. Para os
Figura 3. Diagrama de pacotes do DAO.
empregado não é a melhor implementação. Esse exemplo mostra
claramente a necessidade de SoC em uma aplicação, permitindo
assim obter maiores desempenhos. De uma forma semelhante,
Stored Procedures (SP) permitem ao programador obter valores
elevados de desempenho, contudo o desenvolvedor deve conhecer em detalhes a linguagem e reconhecer que também não são
todos os pontos ou partes do sistema que devem ser implemen-
Figura 4. Diagrama de classes do DAO.
59
: : www.mundoj.com.br : :
tados no banco de dados. Para esse caso simples de uma troca, o
problema em C torna-se trivial, mas para outros tipos de rotinas se
torna praticamente inaceitável o uso de linguagem de montagem.
A linguagem C com o uso da inner function – asm() permite
facilmente a integração de código de máquina em um programa
estruturado. Pode-se constatar também que o desenvolvimento
em linguagem C em ambientes Linux é totalmente facilitado pela
adoção de técnicas de depuração como o uso da ferramenta de
depuração como o GDB (ver links).
Para permitir uma análise comparativa semelhante, foram feitas
medidas de desempenho em Java usando JDBC, Bash/Linux e
também o pgAdmin. As figuras 3 e 4 mostram os diagramas de
pacotes e também de classes usados neste artigo para os testes em
Java. Para o desenvolvimento foi usado o Design Pattern DAO, do
livro J2EE Core (ver livros) e também o Framework JUnit 4.0 para
realizar testes de Unidade e Integração com o DAO.
A figura 5 mostra o modelo DER do diagrama do banco de dados
criado em Postgres/SQL e também duas SPs criadas para a persistência e remoção de dados do banco (típicas rotinas de CRUD
– Create, Recover, Update, Delete).
Figura 5. Modelo DER do banco de dados testado.
A Listagem 5 mostra a criação da tabela pessoa do modelo anterior, repare que nessa listagem foi omitida as outras tabelas, mas a
sua criação segue o mesmo princípio em SQL.
Listagem 5. Procedure em Postgres para a inserção de vários registros no banco.
CREATE TABLE mundoj.pessoa
(
A Listagem 6 mostra a SP em Postgres/plpgsql para a inserção de
apenas um registro no banco de dados e a Listagem 7 mostra uma
rotina com um loop para inserir vários registros no banco.
Listagem 6. Procedure em Postgres para a inserção de apenas um registros no banco.
CREATE OR REPLACE FUNCTION mundoj.fn_insere(character varying)
RETURNS void AS
$BODY$
DECLARE
pnome ALIAS FOR $1;
BEGIN
INSERT INTO mundoj.pessoa (nome) VALUES(pnome);
END;
$BODY$
Listagem 7. Procedure em Postgres para a inserção de vários registros no banco.
CREATE OR REPLACE FUNCTION mundoj.fn_insere_pessoa(integer)
RETURNS void AS
$BODY$
DECLARE
total ALIAS FOR $1;
i INTEGER;
BEGIN
i:=1;
LOOP
INSERT INTO mundoj.pessoa (NOME) VALUES(‘EVA’);
i:=i+1;
EXIT WHEN i>total;
END LOOP;
END;
$BODY$
Além das SPs para inserção de dados foram desenvolvidos scripts
em Bash/Linux para simular a inserção de vários registros no banco com e sem o uso de SPs. As listagens 8 e 9 mostram os scripts
em Bash/Linux para simular esse resultado para 1.000 registros,
o restante do script foi omitido por simplicidade. Os valores de
tempo foram obtidos com o uso do comando time do Linux, um
exemplo de saída é mostrado (Listagem 10).
Listagem 8. Procedure em Postgres para a inserção de vários registros no banco.
id serial NOT NULL,
if [ “$1” -eq “1000” ]; then
nome character varying(255) NOT NULL,
echo “processando 1000 inserts”
endereco character varying(255),
for i in {1..1000};
cpf character varying(11) NOT NULL,
do
data_nascimento date,
site character varying(255)
);
done
ALTER TABLE mundoj.pessoa ADD CONSTRAINT pk_id_pessoa PRIMARY
fi
KEY(id);
60
psql -U postgres -h 127.0.0.1 mundoj -c \
“INSERT INTO mundoj.pessoa (nome) VALUES (‘eva${i}’)” > /dev/null
Listagem 9. Procedure em Postgres para a inserção (via
Stored Procedure) de vários registros no banco.
cont. Listagem 11. Interface DAO para o objeto Pessoa.
public Pessoa retornaPessoaPeloId(int id) throws SQLException;
if [ “$1” -eq “1000” ]; then
echo “processando 1000 inserts”
for i in {1..1000};
do
psql -U postgres -h 127.0.0.1 mundoj -c \
“SELECT mundoj.fn_insere(‘eva’)” > /dev/null;
done
fi
/**
* Método que atualiza uma pessoa no banco de dados. A atualização
* ocorre pelo id
*
* @parm Pessoa um objeto do pacote br.com.altec.model
* @exception
Listagem 10. Obtenção dos valores de tempo.
time /opt/PostgreSQL/8.4/bin/psql -U postgres -h 127.0.0.1 estudos -c “SELECT mundoj.fn_insere(‘eva’)” > /dev/null;
real 0m0.127s
user 0m0.004s
sys 0m0.008s
A partir da Listagem 10 pode-se constatar três estatísticas de tempo. Estes tempos consistem de (I) o tempo real de invocação e
término da execução do comando psql, (II) o tempo de CPU do
usuário, ou seja, o tempo de processamento apenas no processador, também conhecido como CPU-Bound, e (III) o tempo de
sistema da CPU, ou melhor dizendo, o tempo de IO-Bound da
CPU para operações de interrupções.
Listagem 11. Interface DAO para o objeto Pessoa.
/**
* Interface que representa um DAO comum para o objeto Pessoa
* @author Erik Aceiro Antonio [email protected]
*/
public interface DAOPessoa {
/**
* Método que insere uma pessoa no banco de dados
*
* @parm Pessoa um objeto do pacote br.com.mundoj.model
* @exception
SQLException - lança uma exception caso não consiga
inserir
*/
public void inserePessoa(Pessoa p) throws SQLException;
/**
* Método que atualiza uma pessoa no banco de dados
*
* @parm id um inteiro válido para a chave da pessoa
* @exception
SQLException - lança uma exception caso não consiga
inserir
*/
public void removePessoaPeloId(int id) throws SQLException;
/**
* Método retorna uma pessoa no banco de dados
*
* @parm id um inteiro válido para a chave da pessoa
SQLException - lança uma exception caso não consiga
* @exception
inserir
*/
SQLException - lança uma exception caso não consiga inserir
*/
public void atualizaPessoa(Pessoa p) throws SQLException;
}
Observe que para o comando time apresentar o resultado corretamente, o Postgres deve ser configurado sem a senha de autenticação, pois em caso contrário o psql irá solicitar uma senha
de autenticação. As listagens 12 e 13 mostram o DAOPessoa e
também o caso de teste no JUnit.
Listagem 12. Implementação do DAO para o objeto Pessoa.
/**
* Classe que implementa a interface DAOPessoa
*
* @author Erik Aceiro Antonio – [email protected]
*/
public class DAOPessoaImp implements DAOPessoa {
/*campos comuns a todos os métodos*/
private ResultSet
resultSet;
private PreparedStatement prepStatement;
private Statement
statement;
private DAOConnector
daoConnector;
private Connection
conn;
/*métodos principais do DAOPessoa*/
public DAOPessoaImp() throws SQLException, ClassNotFoundException {
conn = DAOConnector.getInstance().conectarBD();
}
@Override
public boolean inserePessoa(Pessoa p) throws SQLException {
int pos=0;
String sql = “INSERT INTO mundoj.pessoa (nome, endereco, cpf, data_
nascimento, escolaridade, email, site) VALUES (?, ?, ?, ?, ?, ?, ?)”;
prepStatement = conn.prepareStatement(sql);
for(int j=1; j<=10; j++){
System.out.println(“resultados para “+(j*100));
long startTime = System.currentTimeMillis();
for(int i=0; i< (100*j); i++){
pos=0;
prepStatement.setString(++pos, p.getNome());
prepStatement.setString(++pos, p.getEndereco());
prepStatement.setString(++pos, p.getCpf());
61
: : www.mundoj.com.br : :
cont. Listagem 12. Implementação do DAO para o objeto
Pessoa.
A figura 6 mostra uma captura do IDE Eclipse para a operação de
inserção da listagem anterior (ver listagens 12 e 13).
prepStatement.setDate(++pos,
new Date(p.getDataNascimento().getTime()));
prepStatement.setString(++pos,p.getEscolaridade());
prepStatement.setString(++pos,p.getEmail());
prepStatement.setString(++pos,p.getSite());
prepStatement.execute();
}
long stopTime
long diff
= System.currentTimeMillis();
Figura 6. JUnit no Eclipse com os tempos de execução para o DAO.
= (stopTime-startTime);
System.out.println(String.format(“total %02d.%03ds “,(diff/60), (diff%60)));
}
return true;
//outros métodos do DAO devem ser implementados aqui
}//Fecha o DAOImp
Listagem 13. Caso de Teste com o JUnit 4.0 para a inserção
de 100 até 1.000 registros.
public class CasoDeTesteInserePessoa {
private DAOPessoaImp dao;
@BeforeClass
public static void setUpBeforeClass() throws Exception {}
@AfterClass
public static void tearDownAfterClass() throws Exception {}
Para fornecer um comparativo entre o tempo de execução das operações de inserção os gráficos das figuras 8 e 9 mostram os tempos
em Java usando instruções preparadas do banco (JDBC), tempo de
execução com e sem o uso de SPs em Bash/Linux, e instruções com
SP executadas diretamente pela interface do pgAdmin.
A partir dos gráficos observa-se que o tempo de execução para cada
uma das aplicações é diferente. A linguagem Java é a que apresentou
um melhor tempo de resposta (quanto mais para baixo for a curva
melhor o tempo). A figura 9 mostra a relação do melhor tempo obtido
em Java em relação ao melhor tempo de execução da Stored Procedure (SP). O melhor tempo da SP foi obtido a partir do comando time
do Linux. A figura 8 também mostra que o desempenho do pgAdmin
é inferior à linguagem Java e também apresenta uma grande instabilidade, mostrando grandes picos dos resultados de inserções. Essa
característica está relacionada com a maneira como o pgAdmin cria
e gerencia os DataSources de conexão. A figura 10 também mostra
a razão de Speedup (melhor tempo em Java sobre o melhor tempo
do banco), a curva de Speedup em verde mostra praticamente que o
desempenho do banco em relação à linguagem Java é muito superior,
o maior valor de Speedup obtido foi de 200 para 1.000 inserções.
@Before
public void setUp() throws Exception {
dao = new DAOPessoaImp();
}
@After
public void tearDown() throws Exception {
dao = null;
}
@Test
public void testInserePessoa() throws SQLException{
Pessoa p = new Pessoa();
p.setNome(“Erik”);
p.setEndereco(“Rio Claro/SP”);
p.setCpf(“33.44.55”);
Figura 8. Tempo de execução para a instrução INSERT/SQL.
// cria uma data
GregorianCalendar greg = new GregorianCalendar();
greg.set(2010, 9, 23);
p.setDataNascimento( greg.getTime() );
// atribui a data para objeto
p.setEscolaridade(“programador”);
p.setEmail(“[email protected]”);
p.setSite(“http://erikblogger.blogspot.com”);
Assert.assertTrue( dao.inserePessoa(p) );
}
}
62
Figura 10. Tempo de execução e Speedup para a instrução INSERT/SQL.
Escalabilidade
Quando o número de acessos no Twitter dispararou, este passou
a usar uma forma combinada e otimizada de MySQL/memcached
e mais de 45 módulos rodando com Apache Cassandra. Este ambiente misto permite hoje 50 milhões de tweets/dia, atualmente
o twitter tem uma base de 125 milhões de usuários, veja (Twitter
& Performance: An Update, http://engineering.twitter.com/). O
Facebook, outro gigante das redes sociais, gera cerca de 60 milhões de fotos por semana. Esses dois cenários mostram a grande
importância da escalabilidade. Escalabilidade está associada com
elasticidade e é uma propriedade de sistemas, redes ou processos
em assimilar uma carga crescente de trabalho de forma comportada. Existem dois tipos de escalabilidade. Escalabilidade Vertical e
Escalabilidade Horizontal.
• Escalabilidade Vertical – um mesmo nó expandido com mais
memória, processadores e discos.
• Escalabilidade Horizontal – aumento no número de nós.
Para se Escalar Horizontalmente existe algumas técnicas como:
Cache, Fila, Master/Slave e Particionamento. Cada uma delas tem
suas vantagens e desvantagens, mas o principal destaque está
associado com alteração da regras de negócios associadas ao mapeamento relacional, tornando dessa forma sistemas de banco de
dados relacionais com interesses e regras de negócios localizadas
no banco mais difíceis de serem escalados, pois nessas situações
é preciso uma reengenharia na estrutura e arquitetura de projeto.
Uma solução alternativa para escalar sistemas atualmente empregada são os bancos de dados NoSQL, que usam mecanismos de
mapeamento baseado em chave-valor, alguns exemplos são Bigtable da Google, Dynamo e CouchDB que usa soluções em JSON.
Manutenção
Neste artigo tratamos de diversos aspectos Transversais, ou seja,
os requisitos não-funcionais. Contudo, interesses de Manutenção
também devem ser considerados. As variáveis de Manutenabilidade
que influenciam o desenvolvimento estão relacionadas com princípios de programação e Orientação a Objetos, como, por exemplo:
Duplicidade de Código; Encapsulamento e Proteção; Flexibilidade
para Reúso; Compreensão do código; Tempo de Desenvolvimento e
Atividades de VV&T (Validação, Verificação & Teste).
Encapsulamento e proteção
Indica a capacidade de se usar código com proteção de acesso.
Neste caso, o princípio de Encapsulamento de um objeto. Em Java
o encapsulamento de um atributo/método é obtido via a palavrachave private. Tornar um campo de uma classe oculta para acesso
externo garante proteção evitando que um campo seja alterado
de forma indevida e diminui o acoplamento com outra classe,
aumentando as chances de reúso. Veja o exemplo da Listagem
14, em que um objeto Mock simula um cadastro de Pedido e o
número do pedido é alterado antes de ser inserido em um banco
de dados, neste caso se o objeto umPedido tivesse sido instanciado com o valor que realmente deveria ser persistido no banco de
dados o objeto que o usuário vê e o que é inserido passam a ser
diferentes tornando a aplicação inconsistente.
Listagem 14. Sem encapsulamento.
/**
* Classe Mock que simula um Pedido
**/
public class Pedido{
int numeroPedido;
//...
}
public class CadastroPedidoMockBD{
public void inserePedido(Pedido umPedido){
Pedido novoPedido = umPedido;
novoPedido.numeroPedido = 1000; /*altera o valor do pedido*/
lista.add(p);
//prossegue e insere no banco – inconsistência !!
}
}
Com a linguagem Java é possível corrigir isso com a palavra-chave
private, essa alteração é mostrada na Listagem 15. No caso de
banco de dados relacionais não existe um operador ou palavrachave que controle o acesso a certas variáveis e que impeça o acoplamento indevido com outros componentes. Vale apena ressaltar,
que mesmo a linguagem em alguns casos é possível acessar um
atributo privado como no caso da Linguagem AspectJ ou através
da Reflection API.
Listagem 15. Com encapsulamento.
/**
* Classe Mock que simula um Pedido
**/
public class Pedido{
private int numeroPedido;
//...
}
public class CadastroPedidoMockBD{
public void inserePedido(Pedido umPedido){
Pedido novoPedido = umPedido;
novoPedido.numeroPedido = 1000; /*não é possível alterar*/
lista.add(p);
//prossegue e insere no banco
}
}
Duplicidade, flexibilidade e compreensão de
código
Duplicidade de código reflete o quanto do código é replicado e
de difícil manutenção. Em alguns casos no banco de dados com
PL/SQL pode-se construir funções que podem ser reaproveitadas,
contudo o reaproveitamento fica limitado a Programação Estruturada. Outro aspecto que deve ser destacado é a capacidade de reúso
que linguagens Orientadas a Objetos fornecem, como é o caso da
Herança e Composição. A não ser que se esteja utilizando um banco de dados Orientados a Objetos, como, por exemplo, o db4 da
Db4Object Inc. a aplicação geralmente fica limitada ao escopo de
programas que usam princípios de Programação Estruturada.
63
: : www.mundoj.com.br : :
Depuração e atividades de VV&T
Comparativo
Um aspecto pouco explorado quanto a Stored Procedures em
banco de dados são as ferramentas de depuração e de Atividades
de Testes VV&T (Validação, Verificação e Testes). Atualmente é
escasso o número de ferramentas que facilitam a depuração e
o teste, a ferramenta dbUnit [2] é uma extensão do JUnit que
permite realizar teste de unidade em banco de dados, mas que
não fornece nenhuma garantia estrutural ou de cobertura. No
caso do Postgres, existe o plugin edb-debugger que é disponível
para download (ver links), esse plugin precisa de uma versão do
Postgres fonte para ser compilado pela comando make do Linux.
Uma alternativa na forma de uma distribuição binária é o uso do
Postgres e pgAdmin 3 distribuídos pela EnterpriseBD (ver links).
A figura 11 mostra o exemplo da Stored Procedure para inserção
sendo depurada pelo pgAdmin [6,7].
A tabela a seguir traz um resumo das principais variáveis discutidas neste artigo e pretende servir como indícios na tomada
de decisões de projetos críticos. Essa tabela é uma adaptação do
modelo de qualidade em Engenharia de Software proposto por
Boehm [10].
Figura 11. Depuração com pgAdmin.
A depuração no pgAdmin é fornecida com um plugin que permite
visualizar valores para cada tipo de variável que está sendo usada
dentro da Stored Procedure através de uma listagem equivalente
de uma consulta em SQL. Ao lado direito é exibida uma tela de
stack, que mostra a pilha de chamada. Um problema no uso do
plugin é que o mesmo vem desabilitado na inicialização do pgAdmin, então é necessário adicionar uma linha no arquivo de configuração do pgAdmin para habilitar o programa na inicialização,
como a configuração do pgAdmin com o plugim de depuração
está fora do escopo deste artigo, procure consultar os links de
referências para obter mais informações. Com o MySQL também
é possível efetuar depuração através do MySQL Query Browser. A
figura 12 mostra também um exemplo de depuração no MySQL
semelhante ao Postgres. Para finalizar, um aspecto importante que
deve ser considerado é a deficiência causada para o desenvolvimento orientado a testes TDD (Test-Driven-Development) quando se foca no desenvolvimento de Stored Procedures com toda a
regra de negócios focada no banco de dados. Nesse caso, abre-se
mão de todo o ciclo de desenvolvimento Red-Green-Refactory e a
facilidade de frameworks como o JUnit.
Figura 12. Depuração com MySQL Query Browser.
64
Manutenabilidade
Transversais
Variáveis
Fator de Impacto
Desenvolvedores PL/SQL
Portabilidade
Desempenho
Escalabilidade
Duplicidade de Código
Encapsulamento e Proteção
Flexibilidade para Reúso
Compreensão do código
Tempo de Desenvolvimento
Atividades de VV&T
Esta tabela agrupa as Variáveis e o seu respectivo Fator de Impacto. Cada Fator de Impacto apresenta três categorias possíveis:
Baixo, representado pela seta vermelha para baixo ( ), Médio ou
Imparcial ilustrado pelo sinal de igual amarelo ( ) e alto com
a seta verde ( ). Essa tabela ilustra uma forma de se medir o
quanto um sistema desenvolvido com código no banco pode ser
influenciado. Neste caso, esses valores foram extraídos a partir do
próprio artigo, contudo, esses valores não são fixos e podem ser
alterados de projeto para projeto.
Considerações finais
Este artigo teve como objetivo apresentar algumas das principais
medidas para se identificar quando usar código no banco ou na
aplicação. O artigo também discutiu a necessidade do uso de SoC
na aplicação e também mostrou como implementar casos de teste
com JUnit para efetuar teste de integração e unidade para obtenção de tempos de execução para o objeto DAO•
Referências
[1] FOWLER, M.. Domain Logic and SQL.
Disponível em <http://martinfowler.com/articles/dblogic.html>
[2] DBUNIT (2010). Site Oficial.
Disponível em <http://www.dbunit.org/>
[3] TIOBE (2010). Índice de popularidade.
Disponível em <http://www.tiobe.com/>
[4] GoF (1995) - Gamma, Erich; Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. AddisonWesley, (1995). ISBN 0-201-63361-2.
[5] HUSTON, V. Padrões de Projeto.
Disponível em<http://www.vincehuston.org/dp/a>
[6] EDB (2010). Edb Debugger.
Disponível em <http://pgfoundry.org/projects/edb-debugger/>
[7] ENTERPRISEDB (2010). Enterprise DB e Postgres.
Disponível em <http://www.enterprisedb.com/>
[8] DANDAMUDI, S. P. (2000). Introduction to Assembly Language Programming: From 8086 to Pentium Processors (Undergraduate Texts in Computer
Science). Sivarama P. Dandamudi, 2ed.
[9] ALUR, D., MALKS, D., CRUPI, J. Core J2EE Patterns, Best Practices and Design
Strategies, 2ed. Prentice Hall, 2 ed. (2003).
[10] BOEHM, B. W., BROWN, J. R., KASPAR, H., LIPOW, M., MACHLEOD, G. J.. Characteristics of Software Quality (1978)
http://www.itweb.com.br/noticias/index.asp?cod=69391
Download