Objetos, tabelas e camada de persistência Geralmente, as aplicações possuem pouca utilidade sem os dados pertinentes ao seu fim. Por exemplo, um site de um jornal, sem as notícias diárias, sem imagens, sem os fatos. Ir a um jornaleiro e comprar a edição impressa nos daria além da funcionalidade, de nos manter atualizados, nos daria também a informação propriamente dita. É necessário entender a importância dos dados e de como eles são processados e armazenados. Em um projeto de software a manipulação dos dados é crucial para o sucesso do projeto. Uma pesquisa que consome muito tempo, um dado mal formatado como saída, imprecisão no armazenamento dos dados, todos esses fatores devem ser analisados e levados em consideração durante o projeto de um software. Ao longo do tempo surgiram vários meios de se realizar o armazenamento dos dados utilizados pelas diversas aplicações. Nos primórdios o armazenamento dos dados era feitos de forma bastante precária, utilizando tecnologias lentas e com capacidade de armazenamento reduzida. Com o passar do tempo as tecnologias de armazenamento evoluíram significativamente. Os equipamentos se tornaram mais ágeis e com capacidade de armazenamento nunca antes imaginada. Acompanhando essa evolução física, também as metodologias empregadas no armazenamento e no controle dos dados evoluíram. Novos paradigmas de programação surgiram a partir da necessidade de melhorar o processo de desenvolvimento de software. Nesse contexto o paradigma estrutural de programação foi gradualmente substituído pelo paradigma de desenvolvimento orientado a objetos. O paradigma de desenvolvimento orientado a objetos nos leva a dividir os grandes problemas em problemas menores. Seu principal objetivo é fazer tudo via objetos, através da interação entre eles e do compartilhamento dos dados sensíveis ao problema, dessa forma solucionando o grande problema [Hayder, 2007]. Atualmente o paradigma de orientação a objetos é amplamente utilizado, este propõe uma visão no qual classes são definidas imitando o comportamento de entidades do mundo real. Objetos são instanciados representando unidades de uma determinada classe. Capaz de conter certos dados e executar ações. Isto é, manipular os dados de entrada a fim de obter uma saída coerente com aquilo que foi planejado e programado. Diversos objetos se comunicam através do uso de métodos previamente definidos. Uma classe pode, através do uso de herança, estender as funcionalidades e atributos de outras classes. Os benefícios do uso da orientação a objetos são diversos entre eles, Hayder, cita: Reusabilidade – Um objeto é capaz de ser utilizado em outro ambiente diferente daquele onde foi planejado. Também é possível, estender um determinado objeto e assim alterar apenas as características que são necessárias aproveitando todo o código já escrito. Refatoração – Ao criar objetos pequenos e bem definidos, torna-se mais fácil a correção e refatoração de um projeto ou aplicação. Capacidade de extensão – Quando for necessário adicionar novas funcionalidades a um sistema já pronto, a extensão de um determinado objeto pode oferecer um meio seguro para realizar esta operação. Uma vez que ao refatorar um determinado objeto, seja alterando-o ou criando uma nova classe que herda de alguma outra já existente, você estará estendendo as funcionalidades do mesmo sem afetar as outras funcionalidades. Manutenibilidade – A programação orientada a objetos oferece uma forma de escrita de código intuitiva e de fácil percepção das ações que estão acontecendo num determinado trecho de código. Eficiência – A orientação a objetos oferece uma vasta coleção de padrões de projeto que maximizam a eficiência no desenvolvimento da aplicação. Em um projeto orientado a objetos, convém que a aplicação seja distribuída em diferentes camadas. Cada camada com uma funcionalidade explicita que deverá ser bem executada e facilmente acoplada ao restante do sistema. Essa visão de camadas é amplamente difundida. É comum a criação de uma camada especifica cuja função é a realização da persistência dos dados da aplicação. Isto é, ela deve ser capaz guardar as informações de maneira coesa e segura. Esta camada deve ser capaz de realizar alterações nos dados persistidos quando necessário, bem como ser capaz também de excluir permanentemente os dados quando necessário e por fim deve ser capaz de fornecer um método para recuperar os dados que foram previamente persistidos. O PHP oferece diversos mecanismos para que o programador realize a persistência dos dados. Entre as alternativas estão o uso de arquivos, arquivos XML, arquivos CSV, diversos drivers para conexão com bancos de dados, entre outros. É comum a escolha do banco de dados para o armazenamento das informações. O PHP utiliza o driver como mecanismo de troca de informações entre a aplicação e o SGBD, para o usuário isso é transparente através do uso de uma API definida. Os sistemas gerenciadores de bancos de dados (SGBDs), como por exemplo, Oracle, MySQL, DB2, entre outros. São sistemas que oferecem mecanismos para gerir o acesso, a manipulação e a organização dos dados em meios físicos. Estes também oferecem interfaces para que os usuários sejam capazes de realizar as operações básicas que foram citadas no anteriormente. Porém a grande maioria dos SGBDs que o mercado hoje oferece é relacional, ou seja, não trabalham com o conceito de orientação a objetos. Isso cria um impasse, pois na camada de aplicação, ou de negócios os dados representados na forma de objetos enquanto o software SGBD trabalha com tabelas e relações entre tabelas. A solução para o problema citado é a criação de um mecanismo que seja capaz de mediar todas as operações que serão executadas nos objetos e aplicá-las a camada de persistência. Existem algumas formas de se fazer isso, por exemplo, na Figura 1 – Relacionamento álbum – artista. Neste exemplo, percebemos que ao transformarmos o modelo entidade- Figura 1 – Relacionamento álbum – artista. relacionamento em um modelo de classes, transformamos o relacionamento em um atributo da classe Albums que faz referência a classe Artistas, dessa forma conseguimos ter coerência entre o que está expresso através das classes com o que está persistido no banco de dados. Porém, é costume que os diagramas de classes sejam construídos a priori com relação aos diagramas entidade-relacionamento. Quando esta abordagem é adotada devemos entender os relacionamentos, entender as chaves primárias e a necessidade da criação de tabelas extras e então realizar o mapeamento. Este é um dos problemas mais comuns quando trabalhamos com programação orientada a objetos e utilizamos bancos de dados relacionais para realizar a persistência dos dados. O conflito objeto-relacional “Enquanto as tecnologia objeto-relacionais supõem a criação de classes contendo a implementação da lógica de negócio, através dos atributos e da implementação de métodos. As tecnologias de manipulação de dados supõem o armazenamento dos dados em tabelas e a manipulação dos mesmos através de uma linguagem de manipulação de dados (data manipulation language – DML)”[SCOTT]. Assumindo as considerações do autor citado, podemos afirmar que existe um conflito objeto-relacional (em inglês numa tradução livre, objectrelational impendance mismatch), uma vez que as tecnologias citadas coexistem na grande maioria dos projetos que são construídos nas empresas atualmente. Enquanto o paradigma de orientação a objetos é fundado sobre uma sólida base de princípios de engenharia de software. O paradigma relacional é fundado sobre princípios matemáticos. Cada qual com suas vantagens e desvantagens. A grande problemática consiste em unir os dois padrões. Porém caso esta junção não seja feita de forma coerente pode ocorrer sérios problemas, como por exemplo, prejudicar a manutenibilidade e o desempenho da aplicação, uma vez que a complexidade da solução adotada pode aumentar exponencialmente a quantidade de código produzido e também reduzir a qualidade do mesmo. Por isso existem vários padrões que buscam simplificar a questão do relacionamento entre objetos (orientação a objetos) e tabelas (entidades relacionais). Estes padrões foram escritos com base em estudos de casos e práticas comuns identificadas em projetos de sucesso. Várias empresas criaram projetos que implementaram esses padrões em diversas linguagens, inclusive no PHP. Um dos maiores problemas é certamente o mapeamento dos atributos entre as classes do domínio e as tabelas do banco de dados. Este é um problema estrutural que pode ser resolvido seguindo-se algumas abordagens como, por exemplo, a organização das tabelas derivando-as do modelo de domínio, de forma que para cada classe exista uma tabela no banco de dados. Existem outros problemas relacionados ao mapeamento objeto relacional como, por exemplo, os problemas comportamentais. Ao trabalhar com uma grande quantidade de objetos, onde estes objetos sofrem alterações durante o tempo de vida da aplicação, existe ainda a possibilidade de um mesmo objeto ser carregado do banco de dados em outra instância da aplicação e sofrer alterações nos seus dados. Para resolver este problema de concorrência podemos utilizar algumas técnicas, como por exemplo o uso de semáforo e o controle de transações oferecido pela maioria dos SGBDs atualmente. Segundo Fowler, “ao carregar uma quantidade de objetos para memória e manipulá-los, deve-se garantir que todas essas modificações foram também realizadas no banco de dados”. Essa afirmação implica em garantir que todas as alterações referentes aos dados da aplicação que foram alteradas durante o processamento de alguma regra de negócio seja também aplicada aos dados que estão persistidos. Como este é um processo com alto custo computacional, uma vez que implica na abertura de uma transação com o banco de dados e na criação de novos objetos. Torna-se preferível gerenciar as mudanças que ocorrem no objeto e as mudanças que ocorrem no banco de dados. Outros problemas podem ainda seguir, como por exemplo, problemas de desempenho e uso excessivo de memória. Para isso recomenda-se seguir as recomendações dos fabricantes do SGBD que será utilizado. Por exemplo, Abhijit Sinha diz que alguns fatores podem prejudicar o desempenho do banco de banco de dados, como por exemplo, uma arquitetura pobre ou mal desenhada, configuração inadequada e ainda problemas de hardware[MySQL ReferencePoint Suite, 2002]. Ao escolher o paradigma de orientação a objetos, fica claro que no projeto em questão será utilizado herança entre as classes. Porém os SGBDs relacionais não oferecem suporte nativo ao uso de herança. Este então é mais um desafio estrutural a ser enfrentado. Fowler apresenta três padrões para resolver o mapeamento entre herança, são eles herança em tabela única, herança com tabela concreta e herança entre tabela classe. No próximo capítulo veremos alguns padrões de projeto que oferecem subsídios para a implementação de sistemas onde os problemas citados anteriormente serão solucionados. Padrões de projetos Segundo Holub, um padrão de projeto é uma técnica geral para resolver um conjunto relacionado de problemas [Holub on Patterns: Learning Design Patterns by Looking at Code, 2004]. Existe uma variedade de padrões para solver um mesmo problema, cada uma partindo de uma visão distinta. Cabe ao analista definir qual será mais adequado ao cenário em questão, levando em consideração o ambiente de software e hardware, os requisitos levantados, a opinião de outros envolvidos, entre outros parâmetros. Algumas boas práticas foram adotas para resolver o conflito objetorelacional. Martin Fowler propôs em seu livro, patterns of enterprise application architecture, um conjunto de padrões de projeto para a resolução deste tipo de problema. Por exemplo, alguns padrões para mapeamento objeto relacional, como, table data gateway, row data gateway, data mapper e o active record. E alguns padrões para tratar mapeamento de herança como, herança em tabela única, herança com tabela concreta e herança entre tabela classe. Table data gateway Fowler define o padrão table data gateway como “o padrão table data gateway irá conter todos os códigos SQL para acesso a uma determinada tabela ou view”. Por exemplo, uma determinada classe pessoa que implementa esse padrão deve ter métodos que acessem a tabela pessoas no SGDB(ver figura 2 – table data gateway). Figura 2 - table data gateway Este padrão é especialmente útil quando as tabelas do banco de dados foram criadas de acordo com as classes do modelo de domínio. Fowler, indica que os métodos implementados retornem coleções, mesmo quando é executado uma pesquisa por um único elemento. Row data gateway Fowler descreve este padrão como uma classe que oferece métodos capazes de retornar objetos exatamente como os registros na base de dados. Um dos maiores problemas dessa abordagem é o custo excessivo gerado pelo uso da conexão com o banco de dados em cada operação de pesquisa. Essa abordagem é especialmente útil quando é necessário um controle rígido sobre as transações. Esta abordagem implica na criação de uma classe contendo métodos estáticos para a pesquisa de um determinado objeto. O usuário ao chamar estes métodos recebe de volta uma coleção de objetos, onde cada objeto possui exatamente os mesmos valores que um determinado registro na tabela que foi pesquisada (ver figura 3 – sequência row data gateway). Figura 3 - sequência row data gateway Data mapper Este padrão é uma camada que busca isolar os objetos que estão em memória dos registros do banco de dados, segundo Fowler. A classe que implementa o padrão data mapper, é responsável por mapear todos os atributos da classe em colunas no banco de dados. Ao utilizar esse padrão é possível oferecer ao programador um alto nível de transparência, ou seja, para ele não é necessário sequer saber qual o SGBD que está sendo utilizado (ver figura 4 – data mapper). Figura 4 - data mapper Active record Fowler define este padrão como sendo uma classe que encapsula um registro no banco de dados, encapsulando também o acesso e as regras de negócio. Os objetos possuem além dos dados e dos métodos comportamentais também possuem métodos para a persistência (ver figura 5 – active record). Este padrão utiliza métodos estáticos para a recuperação dos dados previamente salvos. Uma das principais desvantagens do active record diz respeito a complexidade do objeto. Quando a implementação das regras de negócio incluem relacionamentos complexos entre as entidades, herança, o active record se torna um problema pois não oferece uma forma simples para a construção desse tipo de regras. Figura 5 - active record Herança em tabela única Para o resolver o problema de herança, uma das possíveis abordagens é colocar todos os atributos das classes filhas numa mesma tabela. Por exemplo, ao considerar a classe pessoa como classe raiz e as classes PessoaFisica e PessoaJuridica como herdeiras da classe pessoa, e a classe fornecedor como herdeira da classe pessoa jurídica(ver figura 6 – modelo domínio pessoa). Seria criada uma única tabela no banco de dados que conteria todos os atributos das classes que herdaram de Pessoa (ver figura 7 – herança em tabela única). Porém esta metodologia poderá causar grandes lacunas na tabela criada, por exemplo, se a tabela em questão atingir um milhão de registros e nenhum objeto da classe pessoa física persistido. A otimização da tabela fica prejudicada, pois haveria uma coluna sem nenhum valor. Figura 6 - modelo domínio pessoa Figura 7 - herança em tabela única Herança com tabela concreta Assumindo o modelo de domínio utilizado anteriormente (ver figura 6 – modelo domínio pessoa). Para cada classe herdeira deverá ser criado uma tabela no banco de dados que conterá além dos dados próprios da classe também os atributos da classe que foi estendida (ver figura 8 – herança com tabela concreta). Esta abordagem implica na replicação de colunas no banco de dados para guardar uma mesma informação, neste caso o nome da pessoa. Isto aumenta a complexidade das pesquisas, por exemplo, caso seja necessário pesquisa todos os clientes que comecem com a letra B, isso implica que a pesquisa será feita em três tabelas. Figura 8 - herança com tabela concreta Herança entre tabela classe Assumindo o modelo de domínio utilizado anteriormente (ver figura 6 – modelo domínio pessoa). Para cada classe herdeira deverá ser criado uma tabela no banco de dados também deverá ser criado uma chave estrangeira que relacionará as classes inferiores com as classes superiores (ver figura 8 – herança tabela classe). Esta abordagem implica o relacionamento entre as tabelas inferiores e as superiores, ao implementar os métodos de pesquisa estes deverão lidar com o relacionamento entre as tabelas dessa forma reduzindo o desempenho da aplicação. Entre os padrões referentes ao mapeamento de herança, deve-se analisar cuidadosamente o produto e definir aquele que mais se adéqüe as necessidades do usuário. Esta análise deve considerar a complexidade do modelo de domínio, os recursos físicos e as exigências do usuário, tornando a decisão mais complexa de ser tomada. Figura 9 - herança tabela classe PHP PHP é uma das linguagens de programação mais utilizadas no mundo atualmente, segundo o site tiobe.com(ver tabela 1 – linguagens de programação). Este site é especializado em definir um ranking com as linguagens de programação mais utilizadas no mundo, levando em consideração os programadores certificados, centros de treinamento, a quantidade de empresas prestadoras de serviço entre outros parâmetros que são analisados. Tabela 1 – Linguagens de programação Variação Posição em Posição em % em Linguagem Agosto de 2009 Agosto de 2008 no período Agosto de 2009 de 08/2008 08/2009 1 1 Java 19.527% -2.04% 2 2 C 17.220% +1.04% 3 4 C++ 10.501% +0.44% 4 5 PHP 9.390% +0.04% 5 3 (Visual) Basic 8.486% -2.37% 6 6 Python 4.489% -0.49% 7 8 C# 4.443% +0.75% 8 7 Perl 4.028% -0.67% 9 10 JavaScript 2.812% -0.08% 10 9 Ruby 2.490% -0.43% 11 11 Delphi 2.337% -0.39% 12 13 PL/SQL 0.982% +0.30% 13 14 SAS 0.817% +0.27% 14 27 RPG (OS/400) 0.752% +0.52% 15 26 ABAP 0.739% +0.51% até 16 16 Pascal 0.675% +0.26% 17 12 D 0.662% -0.69% 18 17 Lisp/Scheme 0.630% +0.25% 19 41 Objective-C 0.612% +0.51% 20 25 MATLAB 0.560% +0.32% Fonte: Tiobe.com Podemos citar o PHP como uma linguagem de programação e também como uma plataforma de desenvolvimento. O PHP possui duas grandes forças, a simplicidade e um grande conjunto de funcionalidades. Enquanto linguagem o PHP incorporou a sintaxe elegante utilizada pelo C sem os problemas provenientes da alocação de memória e do uso de ponteiros. O PHP também herdou os poderosos construtores do Perl – porém sem a complexidade normalmente associada aos scripts Perl. Como plataforma, o PHP oferece um poderoso conjunto de funcionalidades que cobrem uma grande gama de necessidades. O PHP também é extensível através de um conjunto bem definido de APIs C, o que torna simples para qualquer desenvolvedor adicionar novas funcionalidades quando necessário [Zend PHP Certification study guide]. Desde a versão 4 é orientado a objetos, o que significa dizer que este desenvolveu o paradigma mencionado anteriormente. Sendo capaz de realizar os conceitos de classe, métodos, herança e as demais facilidades providas pela orientação a objetos. O PHP oferece nativamente classes para conexão com diversos SGBDs e uma API concreta e fácil de usar para a manipulação de arquivos, o que torna a persistência de dados mais fácil de ser implementada. Várias empresas e grupos de usuários oferecem soluções para a camada de persistência de dados, normalmente implementando alguns padrões de projeto como os que foram mencionados anteriormente. Zend framework Por exemplo, a zend foundation, ao lançar o framework Zend, ofereceu a comunidade uma solução em persistência através de um conjunto de classes. Essa implementação se deu através de dois padrões de projeto: table data gateway e row data gateway. Definindo uma série de classes, com métodos e atributos específicos que podem ser estendidas a fim de se obter um mapeamento objeto relacional sem muito esforço e com um custo computacional reduzido. Este framework provê também certo nível de segurança, pois oferece um conjunto de métodos de validação que são capazes de prevenir alguns tipos de injeções de código malicioso que porventura os usuários tentem utilizar. A camada de persistência oferecida pelo Zend Framework é capaz de utilizar diversos SGBDs alterando apenas o objeto pelo qual é realizado a conexão com o banco de dados. Propel framework Outro exemplo de persistência em PHP é o framework propel, que propõe o uso de arquivos de configuração em XML como principio para o mapeamento objeto relacional. Este projeto é baseado no Apache Torque, um framework para realização de mapeamento objeto relacional escrito em Java que segue os mesmos princípios de configuração através do uso de arquivos XML. Também este framework oferece a possibilidade de se trabalhar com diferentes SGBDs entre eles podemos citar, MySQL, MSSQL, Oracle, DB2 e PostgreSQL. Doctrine O doctrine é um framework mantido pela própria comunidade e liderado por Jonathan H. Wage. Este foi inspirado no Hibernate, um framework de persistência escrito em Java. A principal facilidade do doctrine é oferecer uma linguagem proprietária de pesquisa (doctrine query languague – DQL), cuja principal função é tornar transparente o uso dos diferentes dialetos utilizados pelos diversos fornecedores de SGDBs. Outra facilidade do doctrine é a implementação do padrão de projeto Active Record. Também podemos citar a capacidade deste framework de trabalhar com estruturas hierárquicas, uso de hooks (métodos que são chamados quando uma determinada condição é encontrada), uso de um sistema de cache para melhorar o desempenho das operações utilizadas.