O mapeamento entre dados relacionais e objetos (ou: “OO or not OO? That is the question”)

Por Otavio Pecego Coelho

Como mapear regras de negócio contra os dados da Base de Dados?

Há quinze anos atrás esta pergunta faria sentido para pouquíssimos projetistas. Neste tempo, teríamos um conjunto de funções em COBOL ou em stored procedures acessando diretamente o arquivo ou a base de dados para realizar a regra de negócio. A programação estruturada era a ferramenta usada para diminuir o acoplamento e aumentar a coesão.

Com a maturação das linguagens e do Design Orientado a Objetos outros mecanismos puderam ser utilizados, como a herança, o polimorfismo e o encapsulamento. Com isto, a camada de acesso a dados, que antes se limitava a um conjunto de primitivas para acesso ao banco de dados ou arquivo, ganhou a função de esconder da regra de negócios tanto as funções quanto a estrutura física necessária para o acesso ao dado (ex.: o DataSet do ADO.Net).

Ao longo da década de 90 um conjunto de padrões (patterns) foi sendo criado para diminuir a denominada impedância entre os dados relacionais e o paradigma da orientação a objetos (OO). Um bom resumo destes padrões pode ser encontrado no livro de Martin Fowler [Fowler] ou no site www.patternshare.org.

Fowler identifica como três os principais padrões para mapeamento entre lógica de negócios e sistema de persistência de dados. São eles: o padrão Transaction Script, o padrão Table Module e o padrão Domain Model. O primeiro reflete a fase inicial dos sistemas OLTP, onde acesso e negócios são separados por uma fina camada de primitivas e a lógica de negócio é implementada por funções. O segundo padrão mapeia objetos com tabelas através de classes. O uso de Typed DataSets na plataforma .NET [ver referências] é um exemplo claro deste padrão. O terceiro abstrai as entidades em mais um nível - ele mapeia em classes de negócio cada linha de uma tabela, permitindo uma implementação mais próxima e transparente à modelagem lógica. Por exemplo, teríamos a classe Agencia com atributos de uma única agência, e utilizaríamos uma coleção para agrupar as agências de um banco (ex.: Collection<Agencia>).

Mas qual destes 3 padrões (ou variação deles) nós devemos usar?

Hoje é grande o número de frameworks para mapeamento entre OO e Relacional. Comprado ou caseiro, estes frameworks são usados na esperança que haja uma melhoria no custo de manutenção e no custo de desenvolvimento de projetos OLTP. Porém, existem fortes indícios de que seus malefícios não sejam tão conhecidos. Custo alto de implementação, baixo desempenho (com alto custo de tuning) e o aumento da complexidade são alguns resultados comuns no design, implementação e uso destes frameworks.

Este artigo procura relacionar a atual preferência pelo uso destes frameworks com a preferência por um estilo ou paradigma de modelagem - a saber, o Domain Driven Design [Evans]. O artigo também revê estilos anteriores à Orientação a Objetos para comparativos. A decisão do uso ou não de frameworks para mapeamento entre OO e Relacional é deixada para o leitor.

Aspectos da Análise e Design Orientado a Objetos

Traceability

A Orientação a Objetos (OO) é hoje um must. Ela traz um conjunto de promessas relativas à qualidade que a torna muito atrativa. Dentre as muitas, a que me parece ser mais forte e desejável é a denominada traceability (rastreabilidade). A idéia básica é que o mapeamento entre o modelo lógico, físico e o código deva ser muito simples, tornando fácil descobrir o impacto de uma mudança lógica no código ou, no sentido inverso, da conseqüência de uma mudança no código em relação ao comportamento lógico. Uma entidade que reconhecemos na vida real (por exemplo, um Banco) é modelada logicamente como uma entidade lógica (a entidade Banco) e implementada fisicamente numa relação direta (a tabela Banco e a classe Banco). A grande vantagem aqui é que a traceability aumenta em muito (teoricamente) a manutenabilidade e a capacidade de reuso, tornando tanto o desenvolvimento de software quanto o seu suporte e manutenção mais baratos. Deste ponto de vista, encapsulamento, herança e polimorfismo são apenas mecanismos usados para chegarmos a estas qualidades maiores.

Faz parte integrante de um projeto o mapeamento entre o modelo lógico e o físico. Um exemplo desta conexão é a categorização de classes proposta por [Jacobson et al] que implica em três categorias de objetos: objetos entidades (que mapeiam dados de entidades em tabelas banco de dados), objetos de controle (com regras de negócio/algoritmos sobre os dados das entidades e dados de entrada) e objetos de interface (que mapeiam a interface com outros sistemas ou usuários/atores). Stereotypes do UML são normalmente usados para indicar a categoria de cada classe do modelo.

No processo proposto por Jacobson a modelagem é feita através de Casos de Uso (Use Cases) que, por sua vez, são constituídos como um conjunto de objetos que interagem para realizar a funcionalidade. O segundo passo do processo é definir a classe destes objetos para, em seguida, categorizá-los.

A figura 1 apresenta a modelagem de um pedido em um restaurante segundo este processo. Nele um pedido do cliente é recebido por uma interface (o garçom) e o cozinheiro é um controlador, enquanto a comanda (o pedido), a receita e os ingredientes são categorizados como entidades.

Cc580623.OOornotOO_01(pt-br,MSDN.10).gif
Figura 1

Entidades, nesta categorização, tornam-se a base para a modelagem física da persistência da aplicação, gerando tanto o modelo estático de classes, quanto, em seqüência, o modelo físico do banco de dados, isto é, o modelo físico de entidade e relacionamentos (E-R).

Com isto fecha-se o circuito: analisamos o problema e seu domínio, compreendemos suas entidades e as ações a serem realizadas como interações entre objetos que representam estas entidades lógicas, e associamos o estado de algumas entidades com a necessidade de persistência. Deste subconjunto de entidades deriva-se o modelo físico E-R.

Ao implementar o código utilizamos classes que descrevem o estado e o comportamento de cada classe do domínio do problema e, em especial, implementamos a persistência apenas para as classes categorizadas como entidades. A abstração da implementação deve esconder as idas e vindas dos dados da base de dados para alcançamos melhor a traceability. Ao final, conseguimos uma programação simples e de fácil manutenção. Para exemplificar, o código da figura 2 lista todas as Agências de todos os Bancos cadastrados no Banco de Dados:

List<Banco> collectionBanco = Banco.getAll();
foreach( Banco b in collectionBanco )
{
	// imprime atributos do banco
	…
	List<Agencia> collectionAgencia = b.getAllAgencias();
	foreach( Agencia a in collectionAgencia )
{
		// imprime atributos da agência
		…
}
}

Figura 2

Neste código, métodos como GetAll (método estático) e getAllAgencias da classe Banco escondem o acesso ao banco de dados para resgatar a lista de todos os bancos e a lista de todas as agências de um banco.

Análise e Modelo de Domínios

A Análise e o Modelo de Domínios visam identificar, organizar e generalizar informações pertinentes para uma esfera de conhecimento, influência ou atividade. Ao focar em questões específicas, como as regras de negócio, ela abstrai outras questões relevantes, mas relativas a outros aspectos como a arquitetura, a segurança, o logging, a auditoria, etc..

Quando hoje fazemos nossa arquitetura com várias camadas, levamos em conta domínios diferentes. Exemplos já bem conhecidos são:

  • O domínio da apresentação: que trata do como mostrar informações ao usuário;

  • O domínio de negócios: que trata de ações específicas das regras que modelam o negócio, como, por exemplo, entidades financeiras e suas ações (depósito, retirada, etc.);

  • O domínio da persistência: que trata do armazenamento e recuperação de dados em sistemas de persistência, como um banco de dados. Este domínio é normalmente implementado através de classes e/ou frameworks que tratam da inserção, atualização, deleção e busca em banco de dados relacionais.

No domínio da apresentação utilizamos classes como Button, ListBox, Form e outros objetos visuais com seu verbos específicos (métodos) para este domínio. O mesmo ocorre quando usamos classes como Conta e Cartão no domínio de negócios, ou as classes DataSet ou Transaction no domínio da persistência.

Devemos constatar que o uso da OO não vem, na maioria das vezes, acompanhado desta diferenciação de domínios. O mais comum é o uso dos mecanismos de encapsulamento, herança e polimorfismo da OO utilizados de maneira desorganizada refletindo aspectos da estruturação de código, mas sem compromisso com a traceability. Muitos são os possíveis motivos: falta de prática com o Domain Driven Design, a falta ou complexidade de de mapeamento, a necessidade de alto desempenho são alguns dos motivos plausíveis.

Um subproduto interessante da Análise e Modelo de Domínios se refere à normalização. Normalização é um processo bem conhecido da modelagem de banco de dados relacionais que define como agrupar e isolar dados de forma a minimizar a manutenção destes em caso de inserções, alterações ou deleções. Ao realizar a análise e modelagem de "cima para baixo" (top-down), a Análise e o Modelo de Domínios proporcionam a normalização dos atributos das classes/entidades de "forma natural". É raro o modelo que necessita passar por um processo de normalização como usado na modelagem bottom-up (de baixo para cima). Ao incluir no processo de análise a classificação dos tipos e dos relacionamentos entre eles (ser => herança, ter => composição, etc.) torna-se "natural" diferenciar um Pedido de um ItemDePedido, e com ele a normalização.

Em resumo, o conceito de traceability é completamente aderente a esta forma de análise, modelagem e implementação baseada em domínios e, por isto, tornou-se uma corrente forte na área de design. O conceito de Domain Driven Design [Evans] é a prática que dirige esta tendência.

Tipos

O uso de tipos fortes ou fracos é outra consideração relevante no contexto do mapeamento entre OO e relacional. O conceito de tipo tem como função garantir (se possível em tempo de compilação) que certas propriedades invariantes sejam respeitadas. Como nos problemas de física do colégio, podemos identificar um erro caso o cálculo da velocidade resulte na unidade m/s2 . A constatação é feita sintaticamente, pois esta unidade é da aceleração - a da velocidade seria m/s.

Na OO, tipos são correlacionados com classes, isto é, com a estrutura de dados e operações que podem manuseá-la.

Linguagens de tipo forte (como VB.Net e C#) são consideradas mais robustas por permitirem achar erros em tempo de compilação. Em contraste, por exigirem mais sintaxe e controle, são também consideradas linguagens menos propícias à rápida prototipação como acontece com linguagens com tipos fracos (Smalltalk é um bom exemplo) que irão indicar erro no momento da execução.

Tipos fortes podem ser problemáticos quando tentamos modelar operações que não respeitam características estáticas. Um bom exemplo é a operação de join entre duas entidades (classes) - ela cria um novo tipo de dados que não pode ser representado de forma automática. Isto traz para a modelagem os seguintes problemas:

  • A explosão de tipos - por exemplo, temos de criar uma classe BancoAgencia para receber o join entre as classes Banco e Agencia;

  • Ou a desistência do uso de tipos do domínio de negócio para o uso de tipos estruturais (arrays e coleções) e o uso de testes de tipo em tempo de execução. No caso, desistimos de Banco e Agencia para modelar o join com a classe DataSet (um tipo estrutural) que contém o resultado do join.

A resolução deste problema para quem não quer perder a tipagem forte e o conceito de traceability pode ser:

  • Não usar join. Para exemplificar, imagine o caso de um relatório contendo todos os bancos e suas agência. Ao não usar o join nós temos de recuperar do Banco de dados todos os objetos do tipo Banco para então navegarmos, um a um, para recuperar as suas Agencias (do tipo Collection<Agencia>). A desvantagem aqui é que o algoritmo tem desempenho ruim e produz muitas idas ao banco de dados (número de bancos vezes o número de agências);

  • Usar o join do SQL para obter a coleção resultante em forma desnormalizada. Com esta em mãos usar um objeto para criar objetos Banco com suas coleções de Agencias (pattern Factory). Neste caso, o desempenho é inversamente proporcional ao número de agências;

  • Usar join retornando novos tipos (semelhante a Visões em Banco de Dados), como uma coleção do tipo BancoAgencia. Esta opção é factível quando as soluções 1 ou 2 acima não forem convenientes e/ou houver pouca necessidade de joins no sistema - o que diminui o impacto da explosão de tipos.

Uma segunda dimensão relativa a este mesmo problema se refere ao número de atributos resultantes de uma operação de projeção (projeção é a operação da álgebra relacional que escolhe um subconjunto de colunas de uma relação e descarta as demais). A projeção é benéfica por eliminar o fluxo grande de dados desnecessários para uma operação, assim como seu gasto de memória.

Infelizmente a implementação de esquemas de projeção também provoca a explosão de tipos. Basta imaginar que teríamos de ter não só o tipo completo (ex.: Banco com todos seus atributos), mas também outros tipos derivados com apenas parte de seus atributos (só o nome, só o número, nome e número sem CGC, etc.). O design de um framework de mapeamento OO com relacional costuma implementar uma das soluções:

  1. Não permitir operações de projeção, trazendo sempre todos os atributos de uma entidade mesmo que isto incorra no acesso e transmissão desnecessário de grande volume de dados;

  2. Modelar e implementar o conceito de objetos parcialmente completos, isto é, objetos que podem ter atributos em um estado não disponível (algo semelhante ao NaN usado no tipo ponto flutuante IEEE). Com isto, cabe a um teste em tempo de execução checar se um atributo está ou não disponível para uma operação;

  3. Implementar tipos segundo uma seqüência de heranças, provendo conjuntos cada vez mais completos.

Modelo Estático x Modelo Dinâmico

É muito comum a análise e modelagem Orientada a Objetos parar ao fim da modelagem estática ou não ser realizada de forma suficiente até o fim da modelagem dinâmica. O motivo para isto é simples: muitas vezes não se vê valor na modelagem dinâmica. A modelagem dinâmica é cara e é realizada freqüentemente na fase de implementação. Outro motivo comum é a falta de requisitos de desempenho que justifiquem esta análise.

A verdade é que a modelagem dinâmica pode ser o grande diferencial entre um sistema com desempenho ou sem. No caso do exemplo da Figura 2 acima, o método GetAll da classe Banco pode esconder uma query que traz de uma vez o join de Banco e Agencia para, em seguida, preencher os atributos do banco e das agências deste. Neste caso, o método getAllAgencias devolve apenas a coleção já preenchida pelo método GetAll. Uma segunda implementação pode deixar a busca para trazer as agências num segundo momento: na chamada de getAllAgencias. A escolha de qual método utilizar depende da regra de negócios. Quando com probabilidade alta temos de tratar da Agencia junto com Banco, então o primeiro algoritmo é melhor. Caso contrário, o segundo é mais interessante.

Outro ponto que pode se beneficiar fortemente da análise dinâmica se refere à questão dos locks ou deadlocks no banco de dados ou outros recursos.

A regra diz que para obter bom desempenho devemos alocar recursos compartilhados (sob concorrência) o mais tarde possível e liberá-los o mais cedo. Isto minimiza o tempo de espera de uma operação devido à espera pela liberação de recursos que estão sob controle de terceiros. Checar esta regra significa examinar com cuidado o modelo dinâmico e garantir que a modelagem e framework de persistência permitam operações de busca (selects) com ou sem lock.

A possibilidade de deadlock também pode ser eliminada com a análise ainda no Modelo Dinâmico. Um deadlock só acontece se duas operações demandarem recursos em ordem diferente. Por exemplo, a operação OpA demanda o recurso K e I, enquanto a operação OpB demanda o recurso na ordem inversa, I e K. Isto torna possível OpA fazer lock em K e OpB fazer lock em I, criando o deadlock. Garantir o acesso sempre na mesma seqüência, seja K-I ou I-K, elimina a possibilidade de ocorrência de deadlock.

A falta de análise dinâmica não é culpa da OO ou da Análise de Domínio, mas ao manter esta dicotomia entre estático e dinâmico elas propiciam o favorecimento de uma em relação à outra.

Modelo Dinâmico em Sistemas OLTP

A Análise Dinâmica nos leva a considerar um sistema OLTP como um sistema de transformação entre camadas:

  • Do Banco de Dados para uma representação interna ótima para a aplicação de algoritmos de negócio;

  • Da camada de Negócios para uma representação ótima para a camada de Apresentação;

A volta do dado do banco de dados para a interface com o usuário segue o sentido inverso.

Neste paradigma existem duas formas comuns de modelagem das classes de negócios:

  1. Em forma de grafo normalizado como na OO, onde submetemos o dado a um processo de deserialização (trazer do formato coleção de tuplas para o formato de grafo de objetos) e serialização (trazer do formato de grafo de objetos para o formato de coleções de tuplas) - como acontece quando utilizamos DataSets tipados ou quando usamos o padrão Factory para criar grafos de entidades;

  2. Respeitamos o formato de coleções de tuplas e operamos sobre elas para criar o formato de saída - como acontece quando não usamos a OO ou quando a usamos, mas modelando via DataSets.

De qualquer forma, este conjunto de transformações intermediárias segue o padrão Pipeline e é análogo ao de ferramentas de B2B e EAI como o Biztalk. A Figura 3 abaixo apresenta o mapeamento entre as camadas de Dados, de Objetos e de Apresentação. Nela, o mapeamento é feito através de setas coloridas. No entanto, o caso real implica ou no uso de estruturas intermediárias (como, por exemplo, DataSets) que podem conter dados desnormalizados para serem mapeados entre as estruturas, ou no uso de DataSets como "a" estrutura de negócios.

Cc580623.OOornotOO_02(pt-br,MSDN.10).gif
Figura 3

Deste modelo sabemos dois fatos:

  • Que a performance final é definida pelo número de transformações e sua complexidade;

  • Que devemos armazenar a meta-definição de cada transformação - o que costuma ser feito com um dos seguintes métodos:

    • No código com classes e métodos que buscam as tuplas e desserializam para as estruturas internas;

    • Ou por descrições externas, análogas a arquivos xslt que definem uma transformação. O padrão Metadata Mapping é um exemplo deste tipo de meta-descrição;

Devemos ter em mente que o tipo resultante de cada transformação deve ser o melhor formato para o destino de acordo com a direção. Este é um dos muitos motivos que levam alguns a homogeneizar a estrutura de envio e recepção de dados entre as camadas utilizando DataSets ou documentos XML como representações naturais para a representação do conceito de coleções de tuplas.

O interessante deste modelo é que a camada de apresentação pode ser substituída por WebServices ou interações com sistemas de filas sem grandes problemas. O mesmo tipo de abstração pode ser feito com o repositório de dados. Não importa se os dados vêm de uma base de dados relacionais ou de outro WebService - a camada de negócio só precisa estabelecer o contrato com a camada de recepção/transmissão de dados.

Por fim, deve-se alertar que a complexidade costuma ser diferente para cada direção - do banco para a tela; da tela para o banco. No primeiro caso, os dados têm um mapeamento relativamente simples, quase um a um entre tela e banco. Na direção oposta, no entanto, as regras de negócio irão realizar validações e operações de negócio e, neste momento, o comum é terem de coletar novos dados do banco (ou cachê) para concluir a validação/operação.

Padrões e Frameworks de Persistência

O desejo de implementar um modelo OO que abstraia a questão da persistência é um tema natural e recorrente. Um grande número de padrões e frameworks têm sido construídos ao longo do tempo e já existe uma boa literatura a respeito [Fowler]. No entanto, dois são os problemas comuns: eles costumam ter um alto custo (de design, implementação e tuning) e um baixo desempenho quando comparado com buscas diretas ao banco de dados.

Como foram mostrados nos parágrafos acima, existem muitas opções que visam a otimização da busca de registros e tempo de lock. Conseguir um balanço entre simplicidade de implementação e desempenho satisfatório é o Santo Graal de quem constrói este tipo de framework. Mas a verdade é que devemos estar preparados para cobrir apenas um conjunto de situações. Muito provavelmente haverá situações que exigirão desempenho que será conseguido apenas se fugirmos do modelo OO e voltarmos para a programação procedural antiga, talvez utilizando stored procedures.

A aderência ao modelo OO costuma ser mais simples nos Casos de Uso de cadastros, por não necessitarem percorrer grafos complexos de objetos. A única exceção ocorre quando necessitamos implementar a paginação de coleções de objetos no caso do número de elementos da coleção resultante ser muito grande. Este é um problema que exige características não usuais em frameworks e pode ter de ser tratado como uma exceção.

Os demais casos podem ou não ser aderentes de acordo com a complexidade do algoritmo e da característica de desempenho exigidas.

É interessante notar que o cadastro de uma aplicação é justamente um dos lugares de menor necessidade de manutenção e do requisito de traceability oferecida pela OO. Por implementarem quase nenhuma regra de negócio, as funções podem se mapeadas muitas vezes por classes CRUD (responsáveis pela operação de Create, Read, Update e Delete junto ao Banco de Dados), tornando pequeno o retorno de investimento devido ao uso de um framework.

[Fowler] afirma que o Modelo de Domínio, implementado com padrões e frameworks de mapeamento OO-Relacional, deve levar em consideração a complexidade e a dinâmica de mudanças das regras de negócio relativas à validação, cálculo e derivações. Se o sistema não é complexo e estará sujeito a poucas mudanças no tempo, não há porque usar o Modelo de Domínios. Outro bom conselho é: fazer frameworks é uma especialidade e pode custar muito. A opção de compra de um produto de terceiro deve ser examinada com cuidado.

Um caso intermediário entre os padrões Transaction Scripts e Domain Model é o padrão Table Module. Ele é mais simples de implementar que o padrão Domain Model e já conta com uma forte ajuda em IDEs como o Visual Studio .Net. No entanto, ele sofre o preconceito por não ser transparente à modelagem lógica, e pode não ser o mais adequado em condições em que a lógica de negócios seja muito complexa. Isto acontece por ele sempre tratar de coleções (tabelas) e, por isto, não ser suscetível ao uso de técnicas de herança e polimorfismo na modelagem de classes.

Padrões de Mapeamento OO-Relacional: Qual é o melhor?

Infelizmente não existem dados experimentais que possam indicar de maneira precisa qual é o caminho a seguir. O que existem são argumentações pró e contra cada tipo de modelagem e implementação. Algumas pesquisas empíricas [Potok et al.] mostram que não alcançamos com a Orientação a Objetos o patamar esperado de produtividade, e o bom uso da programação estruturada pode, por vezes, obter resultados melhores. Na conclusão do artigo os pesquisadores concluem: "Uma análise de softwares desenvolvidos comercialmente não mostra uma diferença significativa entre a produtividade observada entre o desenvolvimento orientado a objetos e o software procedural".

Os defensores do Modelo de Domínio costumam indicar como um dos seus benefícios o aumento do reuso. No entanto, seu custo pode ser grande devido a algoritmos lentos e transformações e movimentações onerosas. Outra questão a ser tratada se refere à experiência com a modelagem OO. Muitas vezes o time de desenvolvimento não está preparado o suficiente para este tipo de modelagem e corremos o risco de ter uma modelagem não compatível com os benefícios que um framework de mapeamento poderia dar.

Já o uso direto de transformações sobre coleções de tuplas pode ser rápido, mas propicia pouco reuso e controle do impacto devido a mudanças. Para diminuir este impacto será necessário criar processos, apoiados ou não por ferramentas, que gerenciem as mudanças ocorridas no design físico do banco de dados, na regra de negócios ou na apresentação final dos dados.

Talvez o mais acertado em muitos casos seja o uso de um modelo misto. Por exemplo, boa parte de uma aplicação OLTP é relativa a cadastros. Neste caso o modelo CRUD pode ser simples e rápido o suficiente, além de facilmente sujeito à automatização através da geração de código por ferramentas. Regras mais complexas, que também exigem tempo de resposta alto, podem ser o caso da exceção. Aqui o uso da desnormalização pode ser muito bem vindo, e o Modelo de Domínio pode substituir a OO em prol de uma programação estruturada.

Hoje este esquema misto já é bem aceito por muitos, mesmo os que usam o Modelos de Domínio. O uso de transformações é comum na construção de relatórios que normalmente utilizam o mapeamento de campos de um comando select do SQL contra um template do relatório. Quando o ASP.NET 2.0 incorpora o mapeamento direto de um GridView com DataSources a intenção é permitir este mesmo tipo de RAD (Rapid Aplication Development) que construtores de relatórios oferecem, para telas de cadastro ou de apresentação de resultados de operações de filtro.

Apêndice - Desempenho e Tipos na Álgebra Relacional

O histórico do desenvolvimento do modelo de Entidades e Relacionamentos e do Banco de Dados Relacional traz à tona todo o esforço de simplificar o uso da álgebra relacional na especificação e implementação de sistemas.

Na álgebra relacional, uma computação é vista como o resultado de operações sob relações: joins, projeções, sorts e operações de conjuntos. Estas operam sobre coleções de tuplas e devolvem ou coleções de tuplas ou valores escalares. Assim, ela pode ser entendida como um conjunto de transformações sobre coleções de tuplas.

Este paradigma tem a identificação de entidades como uma preocupação importante, mas derivada. Ela é derivada por ser advinda de um processo de normalização, que tem em vista minimizar a duplicação de dados e seus impactos negativos para a manutenção. Ao contrário da OO, aqui a normalização foi concebida de maneira bottom-up.

Ao privilegiar transformações de coleções de tuplas de maneira bottom-up, tornou-se possível utilizar estruturas de dados (como árvores B+) e algoritmos altamente eficientes de sort e merge para a realização de computações. Isto pode implicar vantagens de ordens de grandezas quando comparado aos algoritmos de navegação em grafos de coleções como ocorre na OO. Uma operação de sort pode ter complexidade O(NlogN), e um merge O(N+M). Isto é muito menos do que o O(N3) que ocorre quando percorremos a cadeia Banco -> Agencia -> Conta.

Para chegar nesta simplificação, a teoria de banco de dados necessitou de uma coleção genérica (coleções de tuplas): tipos existem apenas para os elementos da coluna de uma linha de uma tabela - tabelas e linhas são agrupamentos genéricos que não mudam de tipo. Isto garante que não haja explosão de tipos devido à criação de novas tuplas a cada join ou união de tuplas.

Outro ponto assumido foi o de não considerar as operações sobre uma determinada tupla como parte de um tipo. Tuplas são meros repositórios de valores e não contam com operações definidas pelo usuário - só contam com as relacionais. Na OO, dados e algoritmos sobre estes dados (métodos) formam um tipo (classe), o que torna mais complexo introduzir uma operação de join de tipos.

Imagine um operador join abaixo:

Join( Collection<Banco>, Collection<Agencia> ) -> Collection( BancoAgencia )

O que devemos entender pelo novo tipo BancoAgencia? Com certeza devemos ter a união dos atributos das classes originais, porém qual é o significado de um método da antiga classe Banco ou Agencia? Este método trabalha sobre um elemento da coleção ou sobre todos que têm um mesmo valor chave (exemplo: todo elemento da coleção cujo NumeroDoBanco é X)?

De fato, a operação de join realiza uma desnormalização dos dados, o que é incompatível com a estrutura lógica de um Modelo de Domínio usado na OO, e isto caracteriza de forma sucinta a incompatibilidade denominada como Impedância entre Modelos OO e Modelos Relacionais.

Agradecimentos

A Mauro Sant'Anna e Alexandre Nardi pelos comentários e revisão.

Agradecimentos

A Mauro Sant'Anna e Alexandre Nardi pelos comentários e revisão.

Referências

Padrões Mencionados:

Transaction Script - http://patternshare.org/default.aspx/Home.MF.TransactionScript

Table Module - http://patternshare.org/default.aspx/Home.MF.TableModule

Domain Model - http://patternshare.org/default.aspx/Home.MF.DomainModel

Metadata Mapping - http://patternshare.org/default.aspx/Home.MF.MetadataMapping

Typed DataSet - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconworkingwithtypeddataset.asp

Livros e Artigos:

[Jacobson et al] Jacobson, Ivar; Ericsson, Maria; Cacobson, Agneta: The Object Advantage - Business Process Reengineering with Object Technology - Addison-Wesley 1994

[Fowler] Fowler, Martin: Patterns of Enterprise Application Architecture - Addison-Wesley 2003

[Evans] Evans, Eric: Domain Driven Design - Addison-Wesley 2004

[Potok et al.] Potok, Tom; Vouk, Mladen: Development Productivity for Commercial Software Using Object-Oriented Methods - Proceeding of the 1995conference of the Centre for Advanced Studies on Collaboration Research - Novembro de 1995

Mostrar: