sábado, 21 de setembro de 2013

The Little MongoDB Book - Capítulo 4 - Modelagem de Dados

Capítulo 4 - Modelagem de Dados

Vamos mudar um pouco de assunto e ter uma conversa mais abstrata sobre o MongoDB.  Explicar novos termos e nova sintaxe é uma tarefa relativamente simples.  Mas ter uma conversa sobre modelagem de um novo paradigma, não é tão fácil.  A verdade é que a maioria de nós ainda esta descobrindo o que funciona ou não nessas novas tecnologias.  É uma conversa que pode ter um início, mas no fim você terá que aprender praticando.
Entre os bancos de dados NoSQL, os banco de dados orientados a documento são os que mais se assemelham com o modelo relacional.  As diferenças são sutis, mas isso não significa que elas não são importantes.

Inexistência de Joins

A principal diferença que você precisa saber é que o MongoDB não possui Joins.  Não sei realmente o motivo pelo qual um sintaxa Join não é suportada, mas que geralmente são vistos como não escaláveis.  Portanto, quando você precisar dividir seus dados horizontalmente, eles deverão ser “juntados” na camada de aplicação.
Para sobrevivermos em um mundo join-less (sem join), temos que implementar esse relacionamento entre documentos, diretamente na aplicação. Essencialmente, para isso precisamos realizar uma segunda consulta para obter os dados relevantes.  A definição do relacionamento não é tão diferente como no modelo relacional.  
Vamos dar um pouco menos de foco para nossos unicórnios e dedicar mais tempos aos nossos funcionários.  A primeira coisa a fazer é criar um funcionário (estou usando um _id explícitos para que possamos construir exemplos coerentes).
db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d730"), name: 'Leto'})
Agora vamos adicionar alguns funcionários e atribuir Leto como seu gerente.
db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d731"),
   name: 'Duncan',
   manager: ObjectId("4d85c7039ab0fd70a117d730")});

db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d732"),
   name: 'Moneo',
   manager: ObjectId("4d85c7039ab0fd70a117d730")});
(Vale a pena repetir que _id pode ser qualquer valor único.  Como você provavelmente usaria o ObjectId na vida real, usaremos ele aqui também.
É claro, que para encontrar todos os funcionários de Leto, devemos executar:
db.employees.find({manager: ObjectId("4d85c7039ab0fd70a117d730")})
Não há nenhuma mágica aqui.  No pior dos casos, a falta de relacionamento ira necessitar apenas uma consulta extra (provavelmente indexada), na maioria das vezes.

Matrizes e Incorporados

Documentos

Só porque o MongoDB não possui relacionamento, não significa que ele não possua alguns truques na manga.  Lembra-se quando rapidamente falamos que o MongoDB suporta arrays na primeira aula deste documento? Acontece que isso é extremamente útil quando se trata de relacionamento muitos-para-um ou muitos-pra-muitos.  Como exemplo, se um simples funcionários pode ter dois gerentes, poderíamos armazená-los em uma matriz.
db.employees.insert({_id: ObjectId(    "4d85c7039ab0fd70a117d733"),
   name: 'Siona',
   manager: [ObjectId("4d85c7039ab0fd70a117d730"),
             ObjectId("4d85c7039ab0fd70a117d732")] })
É interessante saber que, para alguns documentos, manager pode ser um valor escalar, enquanto que para outros pode ser um array.  A consulta original funcionará em ambos:
db.employees.find({manager: ObjectId("4d85c7039ab0fd70a117d730")})
Você vai descobrir que matrizes de valores são muito mais convenientes que lidar com relacionamentos muitos-pra-muitos de tabelas.
Além de matrizes, MongoDB suporta também documentos incorporados. Vá em frente e tente inserir um documento com um outro documento alinhado, tal como:
db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d734"),
   name: 'Ghanima',
   family: {mother: 'Chani',
            father: 'Paul',
            brother: ObjectId("4d85c7039ab0fd70a117d730")}})
Caso você esteja se perguntando, documentos incorporados podem ser consultados utilizando uma notação de ponto:
db.employees.find({'family.mother': 'Chani'})

DBRef

O MongoDB suporta algo conhecido como DBRef que é uma convenção suportada por diversos drivers. Quando um driver encontra um DBRef ele pode automaticamente localizar o documento de referência.  Um DBRef é composto pelo nome da coleção e o id do documento de referência.
Ele é especificamente útil quando precisamos fazer relacionamento entre documentos de coleções diferentes.

Desnormalização

Uma alternativa para o uso de relacionamento é a desnormalização de seus dados.  Historicamente, a denormalização foi utilizada para código sensíveis à performance, ou quando o dado precisa ser snapshotted (como em um log de auditoria).  No entanto, com a crescente popularidade do NoSQL, muito dos quais não possuem relacionamentos, desnormalização é cada vez mais comum na modelagem.  
Isto não significa que você deve duplicar pedaços de informação em todos os documentos.  No entanto, ao invés de deixar o medo dos dados duplicados conduzir suas decisões de design, considere modelar seus dados com base nas informações pertencentes ao documento.
Por exemplo, digamos que você esta desenvolvendo um aplicativo de fórum.  A maneira mais tradicional de relacionar um usuário específico a um artigo é através de seu userid dentro dos artigos.  Com este modelo, você não pode carregar os artigos sem relacionar com o usuário.
Uma alternativa possível é simplesmente gravar o nome e também o userid em cada artigo.  Você pode fazer isso até mesmo com um documento incorporado como user: {id: ObjectId('Something'), name: 'Leto'}.  Sim, se sua aplicação permite que o usuário mude seu nome, você terá que atualizar cada documento (o que pode ser feito com apenas um comando de update).
Adaptar-se a esse tipo de abordagem não é fácil para alguns.  Em muitos casos, nem sequer faz sentido.  Não tenha medo de experimentar.  Não é adequado apenas em algumas circustâncias, mas também pode ser o caminho certo a fazê-lo.

Qual você deve escolher?

Matrizes de ids são sempre uma estratégia útil quando se trata de cenários um-pra-muitos ou muitos-pra-muitos.  Seguramente DBRefs não são usados com muita frequência, embora você possa experimentar e brincar com eles.  Isso certamente deixa alguns desenvolvedores inseguros sobre como utilizar documentos incorporados versus referência manual.
Em primeiro lugar você deve sabe que o MongoDB limita o tamanho máximo de um único documento a 16 megabytes.  Sabendo deste limite, embora bastante generoso, lhe dá uma idéia de como devem ser utilizados.  Neste ponto, a maioria dos desenvolvedores apoiam as referências manuais para grande parte de seus relacionamentos.
Documentos incorporados são frequentemente aproveitados, principalmente, quando queremos que pequenos pedaços de dados sejam trazidos junto com o documento principal numa única consulta. Um exemplo que usei na vida real foi armazenar um documento accounts em cada usuário, algo como:
db.users.insert({name: 'leto',
    email: 'leto@dune.gov',
   account: {allowed_gholas: 5,
             spice_ration: 10}})
Isto não significa que você deva subestimar o poder dos documentos incorporados ou considerá-los como algo de menor utilidade.  Defina seu modelo de dados de forma que seus objetos não precisem de relacionamento.  Lembre-se que o MongoDB permite consultas e índices sobre campos de documentos incorporados.

Poucos ou Muitas Coleções

Tendo em mente que coleções não possuem esquema, ou seja, são schema-less, é perfeitamente possível construir um sistema usando apenas uma coleção com uma mistura de documentos.
Mas pelo que tenho visto, a maioria dos sistemas MongoDB são definidos de forma semelhante ao que você encontraria em sistemas relacionais.  Em outras palavras, se temos uma tabela no relacional, provavelmente teremos uma coleção no MongoDB (muitos-pra-muitos e relacionamento são uma exceção).
A conversa fica ainda mais interessante quando consideramos documentos incorporados.  O exemplo que frequentemente surge é de um blog, onde temos os comentários inconporados a cada artigo do blog.
Não há nenhuma regra rígida (além do limite de 16MB). Trabalhar com diferentes abordagens fará você ter uma noção do que fazer e do que não fazer.

Neste Capítulo

Nosso objetivo neste capítulo foi fornecer informações úteis para a modelagem de seus dados.  A modelagem de um sistema orientado a documento é diferente, mas não muito diferente do mundo relacional.  Você tem um pouco mais de flexibilidade e um certo constrangimento, mas para um sistema novo, as coisas tendem a se encaixarem melhor.  A única forma pra dar errado é se você não tentar.