Introdução a Bases de Dados para Objectos

Quando falamos de bases de dados, os sistemas de Gestão de Bases de Dados Relacionais (MySQL, SQLite, Postgres, DB2, etc) são sem dúvida os sistemas que todos os programadores conhecem. Desde o seu aparecimento nos anos 70, e com o desenvolvimento da linguagem Structured Query Language (SQL), os sistemas relacionais conquistaram uma posição dominante no mundo da gestão de dados.

Criados em torno do principio simples de que os dados podem ser representados em pequenas entidades tabulares de 2 dimensões, compostas por linhas e colunas, que são depois relacionadas entre si através de chaves identificadoras, os sistemas relacionais mostraram, vezes sem conta, o seu valor. Este é um facto indiscutível.

Programação Orientada a Objectos (POO) é também, e por mérito próprio, um sistema largamente adoptado e com provas dadas no mercado de aplicações. Com uma evolução estável e utilizado por uma fatia significativa de programadores, disponível através de uma vasta selecção de linguagens de programação, com ferramentas, métodos de desenvolvimento e os mais diversos acessórios para a sua correcta utilização, POO é um paradigma que veio para ficar e triunfar. Este é um facto indiscutível.

Com os dois factos anteriores, chegamos ao ponto onde é comum a união das duas áreas, de um lado o poder das bases de dados relacionais e de outro a força da programação orientada a objectos. E talvez esta afirmação nos faça roçar o grande problema: ligar um sistema desenvolvido com base em objectos a um sistema que vê todos os dados como seres bi-dimensionais exige um esforço de tal forma significativo que, efectivamente, existe um fosso entre os dois paradigmas.

Consideremos o desenvolvimento típico de uma aplicação usando tecnologias que implementem os dois paradigmas:

O arquitecto do sistema irá pegar nos requisitos que os clientes indicaram, através de uma qualquer método, possivelmente UML, vai transformar esses requisitos num modelo de objectos que não só é um monumento à utilização de herança, polimorfismo e os demais conceitos de POO, como ainda torna os requisitos do cliente algo cativante de implementar. Munido desta obra prima, o arquitecto passa o fruto do seu trabalho ao administrador da base de dados que o irá dissecar, transformar num diagrama Entidade-Relacionamento, ou outro qualquer que lhe seja familiar, rever relações, criar tabelas e configurar um servidor que, em muitos casos, é uma peça de tecnologia tão afinada e sensível como um relógio suíço.

Findo estas duas etapas, o arquitecto e o administrador reúnem-se para perceber como é que dois modelos, tão diferentes podem comunicar como é esperado e dessa reunião nasce, tipicamente, uma classe para cada tabela, um conjunto de quatro procedimentos base por cada tabela (o comum CRUD: create, retrieve, update e delete) e uma imensa framework de código para gerir tudo isto.

Mesmo usando ferramentas de apoio, como as tecnologias de Object/Relational Mapping (ORM), todo este processo é moroso e complexo, e ainda não começámos a implementar o produto que o cliente pediu.

Para tornar o processo mais simples, um dos lados poderia ser alterado. Ou alteramos a linguagem de programação ou alteramos a base de dados. Se pensarmos em alterar a linguagem facilmente chegamos à conclusão que, qualquer que seja a linguagem, nunca será perfeitamente compatível com a visão tabular das bases de dados relacionais. Por outro lado, remover a base de dados poderá ser uma boa opção: e se, de alguma forma, pudéssemos ter uma base de dados que aceitasse os objectos que tão úteis nos são e não precisasse de os transformar?

Linguagem + Persistência

Sistemas de Bases de Dados para Objectos (OODBMS) são sistemas nos quais a representação da informação é feita através de objectos, tal como em POO, e aos quais é adicionada a facilidade de acesso por linguagens Orientadas a Objectos (OO) através da adição de capacidades de POO. Estes sistemas estão sempre associados a uma linguagem POO existente (ex: Java ou C#), fazendo uso da mesma representação interna de um objecto, e guardam directamente os objectos que os programadores usam no desenvolvimento. Embora ligadas a uma linguagem específica, muitos destes sistemas possuem mecanismos de conversão de modo a que uma aplicação feita numa linguagem possa ser convertida para outra linguagem diferente mantendo todos os registos existentes na base de dado.

Como tantas outras tecnologias, OODBMS surgem da investigação académica em meados dos anos 80, e progridem até à actualidade de forma algo atribulada. Sem grande adopção pelo mercado, vêm ser criada uma organização que pretendia desenvolver um standard que promovesse a sua utilização. Essa organização, Object Data Management Group (ODMG), veio a ser substituída pela actual Object Management Group (OMG) que se torna assim na organização responsável por desenvolver e promover um standard para a tecnologia de bases de dados para objectos.

Neste momento, o OMG, pretende apresentar um standard que ajude o mercado de bases de dados para objectos, mas embora um standard definitivo e oficialmente adoptado ainda não exista, do esforço surgiram várias tecnologias actualmente em uso, tais como as Queries Nativas usadas em vários motores de bases de dados para objectos, ou sistema LINQ da plataforma .Net.

Vantagens

Bases de dados orientadas a objectos são ideais em conjunto com linguagem OO porque eliminam qualquer processo de conversão para a persistência dos dados. O modelo que o arquitecto do sistema desenvolveu será exactamente o mesmo que será usado na base de dados, as relações, os objectos, os atributos, tudo se manterá igual e para os programadores não há mais a necessidade de decorar estruturas de tabelas ou procedimentos de acesso, basta enviar os objectos para o motor de bases de dados e rever os mesmos objectos quando necessário se aplicar qualquer conversão. Isto acontece porque o motor usa exactamente o mesmo modelo de dados que a linguagem de programação.

Com o uso destas bases de dados ultrapassamos vários problemas:

  1. Relações e objectos complexos. É possível guardar vários objectos relacionados por herança ou composição e com vários níveis de complexidade sem qualquer intervenção do programador;
  2. Não há duas linguagens (uma para a BD e uma para a aplicação). Há apenas uma linguagem comum que funciona em todo o projecto. Menos linguagens traduzem-se em melhor desenvolvimento, facilidade de depuração e de eliminação de erros, necessidade de menores conhecimentos que se podem transformar em custos menores, etc.;
  3. Remoção do problema de Impedence Mismatch. O tempo perdido a mapear objectos para tabelas e os problemas quando um objecto não é relacionado directamente para uma tabela (ver ponto 1), é completamente eliminado. Esta característica ajuda na performance final da aplicação;
  4. Existe apenas um modelo de dados. Como podemos ver na introdução, dois modelos de dados distintos oferecem muitos problemas que aumentam a complexidade do projecto ou os seus custos;
  5. Não há necessidade de identificadores únicos para cada objecto, como existe para cada registo de uma tabela através das chaves primárias, toda a identificação dos objectos é transparente ao programador.

OODBMS são também sistemas excelentes para guardar informação com relações e/ou representações complexas, que irão tirar todo o partido da linguagem OO e consequentemente da facilidade de utilização do motor bem como das optimizações que os motores contêm possibilitando um nível de performance superior ao dos RDBMS, tida por alguns autores como 10 a 1000 vezes superior.

Esta performance pode ser explicada, principalmente, por dois pontos:

  • A falta de Impedence Mismatch, como mencionado acima;
  • Optimização para Traversal. Este é o processo pelo qual se percorre o grafo que representa as relações dos nossos objectos, indo de nó a nó para obter os dados.

Na secção da bibliografia são apresentados recursos onde o leitor pode aprofundar o tipo de benchmarks e os resultados comparativos entre OODBMS e RDBMS, e do acesso nativo que os OODBMS oferecem em detrimento de acesso com ORMs e outras técnicas para RDBMS. No entanto, qualquer benchmark feito é dependente da tarefa a testar e se em alguns pontos os OODBMS podem mostrar ganhos significativos, noutros as diferenças serão negligenciáveis ou até demonstrar perdas.

Olhando para a globalidade dos valores, podemos aceitar que os OODBMS oferecem boas perspectivas de performance quando comparados com outros métodos de acesso a RDBMS e como regra geral devemos ter em atenção o tipo de dados e se estamos ou não a trabalhar com objectos. Quanto mais simples for o tipo de dados, maiores poderão ser as vantagens em usar um RDBMS e SQL, mas à medida que a complexidade aumenta, a velocidade obtida com OODBMS é vantajosa. Se trabalharmos com objectos não deveremos descartar os OODBMS.

Código/Exemplo

Vamos desenvolver uma pequena aplicação de gestão de contactos usando um motor de bases de dados para objectos livre, disponibilizado sob a licença GPL, chamado DB4O.

Descrição: Pretendemos uma aplicação desktop para gerir contactos pessoais. Deverá ser possível guardar nomes, números de telefone, moradas e datas de nascimento dos contactos. Precisamos de fazer algumas pesquisas pelo que é valorizado a existência de um sistema simples que permita encontrar os contactos. Deverá ser possível guardar a lista de contactos para uso futuro.

Com a descrição anterior criamos duas classes: a nossa classe principal, Agenda, responsável por fornecer todos os métodos de uma agenda digital, e a classe que representa os nossos contactos, Contacto.

Transformando em código:

public class Contacto {

    private long id;
    private String nome;
    private String apelido;
    private String dataNascimento;
    private String telefone;
    private String telemovel;
    private String endereco;
    private String codigoPostal;
    private String localidade;

    public Contacto() {
        //DO NOTHING
    }

    public Contacto(String nome, String apelido, String dataNascimento, 
        String telefone, String telemovel, String endereco, 
        String codigoPostal, String localidade) {
        
        this.nome = nome;
        this.apelido = apelido;
        this.dataNascimento = dataNascimento;
        this.telefone = telefone;
        this.telemovel = telemovel;
        this.endereco = endereco;
        this.codigoPostal = codigoPostal;
        this.localidade = localidade;

        id = hashCode();
    }
    
    //Restantes getters, setters, equals e hashCode
    (...)
}

A classe que nos interessa e que regista os dados é a classe Agenda. O nosso motor está acessível através de uma instância de ObjectContainer. Esta classe oferece os métodos base para guardar, remover e pesquisar objecto guardados e é o nosso ponto de ligação com o motor. Como podem reparar é um objecto Java, não um método para injectar SQL a ser executado num servidor.

public class Agenda {
    private ObjectContainer db; //<-- Motor de bases de dados

    public Agenda(String ficheiro) {
        abrirDb(ficheiro);
   }

    public void fechar() {
        if (db != null) {
            db.close();
        }
    }
//...

Neste exemplo estamos a usar uma base de dados representada por um ficheiro. Seria possível usar um servidor remoto e aceder por rede, no entanto, para simplificar o código e porque é apenas uma apresentação, será usado o acesso embutido e um ficheiro local.

Estamos também a usar o motor como parte da nossa aplicação, ele executará enquanto a aplicação estiver aberta e terminará quando a aplicação fechar. Todos os parâmetros de configuração são os de omissão. Se o ficheiro não existir será criado, se existir serão usados os dados já guardados.

Ao abrirmos a base de dados obtemos a instância de ObjectContainer que precisamos para trabalhar os nossos dados.

private void abrirDb(String ficheiro) {
    db = Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(), ficheiro);
}

E chegamos ao primeiro método que nos dará controlo sobre os dados, o método store permite guardar ou actualizar o nosso objecto. Se o objecto é novo então motor vai inserir o objecto, se é um objecto que já existe na base de dados e foi modificado, então o motor vai actualizar os dados na base de dados. A par com o método store, temos também o método delete, que como seria de esperar permite remover o objecto que é passado como parâmetro.

public void adicionarContacto(Contacto contacto) {
    db.store(contacto);
}

public void removerContacto(Contacto contacto) {
    db.delete(contacto);
}

public void actualizarContacto(Contacto contacto) {
    db.store(contacto);
}

Estes métodos mostram uma vantagem no uso deste tipo de motores de bases de dados. Neste caso o programador não precisa de saber SQL ou outra linguagem especial para manipulação de dados, lembrem-se: o motor está a guardar os nosso objectos Java e está ligado à nossa linguagem de programação Java.

Podemos usar directamente a linguagem para a pesquisa de dados e dentro do método de pesquisa podemos colocar a lógica que precisarmos para determinar se o objecto que nos é mostrado é o que queremos ou não.

O motor vai executar o método match para os objectos existentes e se o método devolver true coloca o objecto em questão numa lista que nos é devolvida no fim. Essa lista contém todos os objectos que o algoritmo de pesquisa identificou.

Este tipo de pesquisas é chamado de native queries, ou pesquisas nativas, porque faz uso directo da linguagem de programação que estamos a usar. Existem outros métodos que veremos mais a diante.

public List<Contacto> pesquisar(final String termo) {
    return db.query(new Predicate<Contacto>() {

        public boolean match(Contacto contacto) {
            if (contacto.getApelido().contains(termo)
                    || contacto.getNome().contains(termo)
                    || contacto.getLocalidade().contains(termo)
                    || contacto.getEndereco().contains(termo)
                    || contacto.getNome().contains(termo) 
                    || contacto.getTelefone().equals(termo)
                    || contacto.getTelemovel().equals(termo)) {
                return true;
            }

            return false;
        }
    });
}

Além das pesquisas nativas existem também as queries by example, ou pesquisas por comparação, e pesquisas especiais que fazem uso da definição da classe para obter resultados. As pesquisas por comparação recebem um objecto do mesmo tipo do que pretendemos pesquisar, com os campos que queremos usar na comparação preenchidos, e devolve objectos que sejam similares. Por exemplo, se quiséssemos procurar todos os contactos com o nome João, seria necessário cria um objecto do tipo Contacto, colocar o nome que pretendemos pesquisar e fornecer esse objecto ao motor. Depois de terminada a pesquisa iríamos receber uma lista com todos os contactos que tinham João no atributo nome.

public List<Contacto> queryByExample() {
    //Numa situação real receberiamos o objecto como parâmetro do método
    Contacto exemplo = new Contacto();
    exemplo.setNome("João");
    
    return db.queryByExample(exemplo);
}

As pesquisas especiais poderão ser dependentes do motor de bases de dados que estamos a usar, neste caso o DB4O oferece um mecanismo que nos permite obter todos os objectos de uma determinada classe. O método seguinte devolve todos os objectos do tipo Contacto que estejam guardados na bases de dados.

public List<Contacto> listarTodosContactos() {
    return db.query(Contacto.class);
}

E com este exemplo simples conseguimos criar todo o sistema de persistência, tudo isto com dois métodos e um ou outro mecanismo de pesquisa. Usámos um método para guardar e actualizar dados, store, um para remover, delete, uma pesquisa nativa que faz uso da linguagem Java, e um método de pesquisa especial por classe. O restante código, que pode ser visto nos ficheiros do artigo, não é mais cobertura de açúcar para criar uma interface e oferecer alguns botões com ícones agradáveis.

Problemas

Mas… se isto é tão bom porque é que não é tão usado no mercado de aplicações?

Esta é uma pergunta difícil de responder. Naturalmente não existem sistemas perfeitos, e as bases de dados para objectos possuem algumas desvantagens. Se olharmos para o factor humano, podemos apontar alguns pontos que, subjectivos em natureza, afectam a escolha da tecnologia:

  • Falta de conhecimento. Como foi dito no início do artigo, quase todos os programadores sabem o que são SGBDs, fazem parte das cadeiras base dos cursos de informática, quer na área geral quer na área de programação. O mesmo não acontece com OODBMs;
  • Inércia e comodismo. É mais simples usar o que já conhecemos;
  • Medo da tecnologia. O desconhecimento da tecnologia leva a que se tenha receio da sua utilização.
  • Medo empresarial. Muitas das empresas que desenvolvem bases de dados para objectos são pequenas e com pouca expressão ou publicidade no mercado, e isso torna provoca algum medo quando é necessário decidir o que usar. O risco de apostar numa empresa pequena parece maior do que o risco de apostar, por exemplo, na Oracle.

Nota: Estes pontos podem também ser vistos como não tendo efeito ou até como vantagens uma vez que as pessoas envolvidas respondem de modo diferente a cada situação apresentada.

Se nos focarmos nos factores técnicos deparamo-nos com:

  • Falta de interoperabilidade. Em parte devido à fraca adesão e à falta de um standard competente (ver também ponto seguinte);
  • Forte ligação à linguagem de programação associada. Implica que os dados presentes num OODBMS só possam ser acedidos de uma linguagem de programação específica. Este problema pode ser eliminado em alguns motores que oferecem ferramentas para conversão do modelo de dados para outras linguagens;
  • Falta de capacidade para pesquisas Ad-hoc. Em RDBMS é possível criar pesquisas que dão origem a novas tabelas através da junção de tabelas existentes (utilização de JOINs, implícitos ou explícitos e criação de tabelas temporárias). Este processo não é possível em OODBMS, não é possível juntar dois objectos de modo a que a sua combinação origine um novo objecto.

Resumindo

Este artigo teve como objectivo despertar interesse nos sistemas de bases de dados para objectos e demonstrar um exemplo prático da sua utilização. Não se pretende que o leitor considere o motor usado no exemplo como a única opção ou como a melhor opção. Existem muitos outros motores, para as mais variadas linguagens.

Como qualquer outra tecnologia, esta oferece vantagens e desvantagens que devem, sempre, ser avaliadas à luz do objectivo do projecto. Os resultados de testes de performance mostram que a tecnologia tem um bom desempenho face às alternativas existentes e que deve ser considerada, especialmente quando lidamos com objectos e linguagens POO.

Como objectivo principal, esperamos que o leitor tenha ficado sensibilizado para a existência da tecnologia de bases de dados para objectos e que a considere aquando da avaliação de tecnologias para os projectos que desenvolve.

Recursos consultados