: : 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