O autor selecionou o Fundo de Internet Aberta/Liberdade de Expressão para receber uma doação como parte do programa Write for DOnations.
Introdução
Se você tem muita experiência trabalhando com bancos de dados relacionais, pode ser difícil ultrapassar os princípios do modelo relacional, como pensar em termos de tabelas e relacionamentos. Bancos de dados orientados a documentos como MongoDB tornam possível romper com a rigidez e limitações do modelo relacional. No entanto, a flexibilidade e liberdade que vem com a capacidade de armazenar documentos autodescritivos no banco de dados pode levar a outros problemas e dificuldades.
Este artigo conceitual descreve cinco orientações comuns relacionadas ao design de esquema em um banco de dados orientado a documentos e destaca várias considerações que se deve fazer ao modelar relacionamentos entre dados. Ele também percorrerá várias estratégias que se pode empregar para modelar tais relacionamentos, incluindo a incorporação de documentos dentro de arrays e o uso de referências filhas e pais, bem como quando essas estratégias seriam mais apropriadas para serem utilizadas.
Diretriz 1 — Armazenar Juntos o que Precisa ser Acessado Juntos
Em um banco de dados relacional típico, os dados são mantidos em tabelas, e cada tabela é construída com uma lista fixa de colunas que representam vários atributos que compõem uma entidade, objeto ou evento. Por exemplo, em uma tabela representando estudantes em uma universidade, você poderia encontrar colunas mantendo o primeiro nome, sobrenome, data de nascimento e um número de identificação único de cada estudante.
Normalmente, cada tabela representa um único assunto. Se você quisesse armazenar informações sobre os estudos atuais de um estudante, bolsas de estudo ou educação anterior, poderia fazer sentido manter esses dados em uma tabela separada da que contém suas informações pessoais. Você poderia então conectar essas tabelas para indicar que há uma relação entre os dados em cada uma, indicando que as informações que contêm têm uma conexão significativa.
Por exemplo, uma tabela descrevendo o status de bolsa de cada estudante poderia se referir aos alunos pelo número de identificação do estudante, mas não armazenaria diretamente o nome ou endereço do estudante, evitando a duplicação de dados. Nesse caso, para recuperar informações sobre qualquer estudante com todos os dados sobre as contas de mídia social do estudante, educação anterior e bolsas de estudo, uma consulta precisaria acessar mais de uma tabela de uma vez e, em seguida, compilar os resultados de diferentes tabelas em um só.
Esse método de descrever relacionamentos por meio de referências é conhecido como um modelo de dados normalizado. Armazenar dados dessa maneira — usando múltiplos objetos separados e concisos relacionados entre si — também é possível em bancos de dados orientados a documentos. No entanto, a flexibilidade do modelo de documentos e a liberdade que ele oferece para armazenar documentos e arrays incorporados dentro de um único documento significa que você pode modelar dados de maneira diferente do que faria em um banco de dados relacional.
O conceito subjacente para modelagem de dados em um banco de dados orientado a documentos é “armazenar juntos o que será acessado juntamente”. Aprofundando-se mais no exemplo do estudante, digamos que a maioria dos estudantes nessa escola tenha mais de um endereço de email. Por causa disso, a universidade deseja a capacidade de armazenar múltiplos endereços de email com as informações de contato de cada estudante.
Nesse caso, um exemplo de documento poderia ter uma estrutura como a seguinte:
Observe que este exemplo de documento contém uma lista incorporada de endereços de email.
Representar mais de um assunto dentro de um único documento caracteriza um modelo de dados desnormalizado. Isso permite que aplicativos recuperem e manipulem todos os dados relevantes para um determinado objeto (aqui, um estudante) de uma só vez, sem a necessidade de acessar múltiplos objetos e coleções separados. Fazer isso também garante a atomicidade das operações sobre tal documento sem precisar usar transações multi-documentos para garantir a integridade.
Armazenar juntos os dados que precisam ser acessados juntos usando documentos incorporados é frequentemente a maneira ideal de representar dados em um banco de dados orientado a documentos. Nas seguintes diretrizes, você aprenderá como diferentes relacionamentos entre objetos, como relacionamentos um-para-um ou um-para-muitos, podem ser melhor modelados em um banco de dados orientado a documentos.
Diretriz 2 — Modelando Relacionamentos Um-para-Um com Documentos Incorporados
Um relacionamento um-para-um representa uma associação entre dois objetos distintos onde um objeto está conectado com exatamente um de outro tipo.
Continuando com o exemplo do estudante da seção anterior, cada estudante possui apenas um cartão de identificação de estudante válido em qualquer momento. Um cartão nunca pertence a múltiplos estudantes, e nenhum estudante pode ter múltiplos cartões de identificação. Se você fosse armazenar todos esses dados em um banco de dados relacional, provavelmente faria sentido modelar a relação entre estudantes e seus cartões de ID armazenando os registros de estudantes e os registros de cartões de ID em tabelas separadas que são ligadas através de referências.
Um método comum para representar tais relações em um banco de dados de documentos é usando documentos incorporados. Como exemplo, o seguinte documento descreve um estudante chamado Sammy e seu cartão de identificação de estudante:
Note que, em vez de um único valor, o campo id_card
deste documento de exemplo contém um documento incorporado que representa o cartão de identificação do estudante, descrito por um número de ID, a data de emissão do cartão e a data de expiração do cartão. O cartão de identidade essencialmente se torna parte do documento que descreve o estudante Sammy, mesmo sendo um objeto separado na vida real. Geralmente, estruturar o esquema do documento dessa forma, de modo que você possa recuperar todas as informações relacionadas por meio de uma única consulta, é uma escolha sólida.
As coisas se tornam menos diretas quando você encontra relações que conectam um objeto de um tipo com muitos objetos de outro tipo, como os endereços de e-mail de um estudante, os cursos que eles frequentam ou as mensagens que postam no quadro de mensagens do conselho estudantil. Nas próximas orientações, você usará esses exemplos de dados para aprender diferentes abordagens para trabalhar com relações um-para-muitos e muitos-para-muitos.
Diretriz 3 — Modelagem de Relações Um-para-Poucos com Documentos Incorporados
Quando um objeto de um tipo está relacionado a múltiplos objetos de outro tipo, isso pode ser descrito como uma relação um-para-muitos. Um estudante pode ter múltiplos endereços de e-mail, um carro pode ter inúmeras peças, ou um pedido de compra pode consistir de múltiplos itens. Cada um desses exemplos representa uma relação um-para-muitos.
Embora a forma mais comum de representar uma relação um-para-um em um banco de dados de documentos seja através de um documento incorporado, existem várias maneiras de modelar relações um-para-muitos em um esquema de documentos. Ao considerar suas opções para modelar essas relações de forma mais adequada, há três propriedades da relação dada que você deve considerar:
- Cardinalidade: Cardinalidade é a medida do número de elementos individuais em um conjunto dado. Por exemplo, se uma turma tem 30 estudantes, você poderia dizer que essa turma tem uma cardinalidade de 30. Em uma relação um-para-muitos, a cardinalidade pode variar em cada caso. Um estudante pode ter um endereço de e-mail ou múltiplos. Eles podem estar matriculados em apenas algumas aulas ou podem ter um cronograma completamente cheio. Em uma relação um-para-muitos, o tamanho de “muitos” influenciará como você pode modelar os dados.
- Acesso independente: Alguns dados relacionados raramente, se é que alguma vez, serão acessados separadamente do objeto principal. Por exemplo, pode ser incomum recuperar o endereço de e-mail de um único aluno sem outros detalhes do aluno. Por outro lado, os cursos de uma universidade podem precisar ser acessados e atualizados individualmente, independentemente do aluno ou alunos que estão matriculados para frequentá-los. Se você vai ou não acessar um documento relacionado sozinho também afetará como você pode modelar os dados.
- Se a relação entre os dados é estritamente uma relação um-para-muitos: Considere os cursos que um exemplo de aluno frequenta em uma universidade. Do ponto de vista do aluno, ele pode participar de múltiplos cursos. À primeira vista, isso pode parecer uma relação um-para-muitos. No entanto, os cursos universitários raramente são frequentados por um único aluno; mais comumente, múltiplos alunos frequentarão a mesma aula. Em casos como este, a relação em questão não é realmente uma relação um-para-muitos, mas uma relação muitos-para-muitos, e assim você adotaria uma abordagem diferente para modelar essa relação do que faria para uma relação um-para-muitos.
Imagine que você está decidindo como armazenar os endereços de e-mail dos alunos. Cada aluno pode ter múltiplos endereços de e-mail, como um para o trabalho, um para uso pessoal e um fornecido pela universidade. Um documento representando um único endereço de e-mail pode ter uma forma como esta:
Em termos de cardinalidade, haverá apenas alguns endereços de e-mail para cada estudante, já que é improvável que um estudante tenha dezenas — quanto mais centenas — de endereços de e-mail. Assim, essa relação pode ser caracterizada como uma relação um-para-poucos, o que é um motivo convincente para incorporar endereços de e-mail diretamente no documento do estudante e armazená-los juntos. Você não corre o risco de que a lista de endereços de e-mail cresça indefinidamente, o que tornaria o documento grande e ineficiente para usar.
Nota: Esteja ciente de que há certas armadilhas associadas ao armazenamento de dados em arrays. Por exemplo, um documento MongoDB não pode exceder 16MB de tamanho. Embora seja possível e comum incorporar múltiplos documentos usando campos de array, se a lista de objetos crescer de forma descontrolada, o documento pode rapidamente atingir esse limite de tamanho. Além disso, armazenar uma grande quantidade de dados dentro de arrays incorporados tem um grande impacto no desempenho de consultas.
Incorporar múltiplos documentos em um campo de array provavelmente será adequado em muitas situações, mas saiba que também pode não ser sempre a melhor solução.
No que diz respeito ao acesso independente, é provável que os endereços de e-mail não sejam acessados separadamente do estudante. Dessa forma, não há um incentivo claro para armazená-los como documentos separados em uma coleção separada. Este é outro motivo convincente para incorporá-los dentro do documento do estudante.
A última coisa a considerar é se essa relação é realmente uma relação um-para-muitos em vez de uma relação muitos-para-muitos. Como um endereço de e-mail pertence a uma única pessoa, é razoável descrever essa relação como uma relação um-para-muitos (ou, talvez mais precisamente, uma relação um-para-poucos) em vez de uma relação muitos-para-muitos.
Essas três suposições sugerem que inserir os vários endereços de e-mail dos alunos dentro dos mesmos documentos que descrevem os alunos em si seria uma boa escolha para armazenar esse tipo de dado. Um documento de exemplo de um aluno com endereços de e-mail inseridos poderia ter essa forma:
Usando essa estrutura, sempre que você recuperar um documento de um aluno, você também recuperará os endereços de e-mail inseridos na mesma operação de leitura.
Se você modelar uma relação do tipo um-para-poucos, onde os documentos relacionados não precisam ser acessados de forma independente, inserir documentos diretamente como este geralmente é desejável, pois isso pode reduzir a complexidade do esquema.
Como mencionado anteriormente, no entanto, inserir documentos como este nem sempre é a solução ótima. A próxima seção fornece mais detalhes sobre por que isso pode ser o caso em algumas situações e descreve como usar referências a filhos como uma forma alternativa de representar relações em um banco de dados de documentos.
Diretriz 4 — Modelagem de Relações Um-para-Muitos e Muitos-para-Muitos com Referências Filhas
A natureza da relação entre estudantes e seus endereços de e-mail informou como essa relação poderia ser melhor modelada em um banco de dados de documentos. Há algumas diferenças entre isso e a relação entre estudantes e os cursos que frequentam, então a maneira como você modela as relações entre estudantes e seus cursos também será diferente.
Um documento descrevendo um único curso que um estudante frequenta poderia seguir uma estrutura como esta:
Digamos que você decidiu desde o início usar documentos incorporados para armazenar informações sobre os cursos de cada estudante, como neste exemplo:
Isso seria um documento MongoDB perfeitamente válido e poderia atender ao propósito, mas considere as três propriedades de relacionamento que você aprendeu na diretriz anterior.
A primeira é a cardinalidade. Um estudante provavelmente manterá apenas alguns endereços de e-mail, mas pode frequentar vários cursos durante seus estudos. Após vários anos de frequência, pode haver dezenas de cursos em que o estudante participou. Além disso, eles frequentariam esses cursos junto com muitos outros estudantes que também estão frequentando seus próprios conjuntos de cursos ao longo de seus anos de frequência.
Se você decidisse incorporar cada curso como no exemplo anterior, o documento do aluno rapidamente se tornaria desorganizado. Com uma cardinalidade maior, a abordagem de documento incorporado se torna menos atraente.
A segunda consideração é o acesso independente. Ao contrário dos endereços de e-mail, é razoável assumir que haveria casos em que informações sobre cursos universitários precisariam ser recuperadas individualmente. Por exemplo, digamos que alguém precise de informações sobre cursos disponíveis para preparar um folheto de marketing. Além disso, é provável que os cursos precisem ser atualizados ao longo do tempo: o professor que ministra o curso pode mudar, seu horário pode flutuar ou seus pré-requisitos podem precisar de atualização.
Se você armazenasse os cursos como documentos incorporados em documentos de alunos, recuperar a lista de todos os cursos oferecidos pela universidade se tornaria problemático. Além disso, cada vez que um curso precisasse de uma atualização, você precisaria passar por todos os registros de alunos e atualizar as informações do curso em todos os lugares. Ambos são bons motivos para armazenar os cursos separadamente e não incorporá-los completamente.
A terceira coisa a considerar é se a relação entre o aluno e um curso universitário é realmente um-para-muitos ou se, em vez disso, é muitos-para-muitos. Neste caso, é o último, pois mais de um aluno pode frequentar cada curso. A cardinalidade e os aspectos de acesso independente dessa relação sugerem contra a incorporação de cada documento de curso, principalmente por razões práticas como facilidade de acesso e atualização. Considerando a natureza muitos-para-muitos da relação entre cursos e alunos, pode fazer sentido armazenar documentos de curso em uma coleção separada com identificadores únicos próprios.
Os documentos representando classes nesta coleção separada podem ter uma estrutura como esses exemplos:
Se você decidir armazenar informações de cursos dessa forma, precisará encontrar uma maneira de conectar estudantes com esses cursos para que você saiba quais estudantes participam de quais cursos. Em casos como este, onde o número de objetos relacionados não é excessivamente grande, especialmente com relacionamentos muitos-para-muitos, uma maneira comum de fazer isso é usar referências filhas.
Com referências filhas, um documento de estudante referenciará os identificadores de objetos dos cursos que o estudante frequenta em um array incorporado, como neste exemplo:
Observe que este exemplo de documento ainda possui um campo courses
que também é um array, mas em vez de incorporar documentos de curso completos como no exemplo anterior, apenas os identificadores referenciando os documentos de curso na coleção separada são incorporados. Agora, ao recuperar um documento de estudante, os cursos não estarão imediatamente disponíveis e precisarão ser consultados separadamente. Por outro lado, é imediatamente conhecido quais cursos recuperar. Além disso, caso os detalhes de algum curso precisem ser atualizados, apenas o próprio documento do curso precisa ser alterado. Todas as referências entre estudantes e seus cursos permanecerão válidas.
Nota: Não há uma regra fixa para quando a cardinalidade de uma relação é muito grande para incorporar referências filhas dessa maneira. Você pode optar por uma abordagem diferente em cardinalidades mais baixas ou mais altas se for o que melhor se adapta ao aplicativo em questão. Afinal, você sempre desejará estruturar seus dados de acordo com a maneira como seu aplicativo consulta e atualiza esses dados.
Se você modelar um relacionamento um-para-muitos onde a quantidade de documentos relacionados está dentro dos limites razoáveis e os documentos relacionados precisam ser acessados de forma independente, prefira armazenar os documentos relacionados separadamente e incorporar referências filhas para conectá-los.
Agora que você aprendeu como usar referências filhas para indicar relacionamentos entre diferentes tipos de dados, este guia irá descrever um conceito inverso: referências pai.
Diretriz 5 — Modelagem de Relacionamentos Um-para-Muitos Ilimitados com Referências Pai
O uso de referências filhas funciona bem quando há muitos objetos relacionados para incorporá-los diretamente dentro do documento pai, mas a quantidade ainda está dentro dos limites conhecidos. No entanto, há casos em que o número de documentos associados pode ser ilimitado e continuará a crescer com o tempo.
Como exemplo, imagine que o conselho estudantil da universidade tem um quadro de mensagens onde qualquer aluno pode postar o que quiserem, incluindo perguntas sobre cursos, histórias de viagem, anúncios de emprego, materiais de estudo ou apenas uma conversa livre. Uma mensagem de exemplo neste caso consiste em um assunto e um corpo de mensagem:
Você poderia usar qualquer uma das duas abordagens discutidas anteriormente — incorporação e referências filhas — para modelar este relacionamento. Se você optasse por incorporar, o documento do aluno poderia ter uma forma como esta:
No entanto, se um estudante é prolífico na escrita de mensagens, seu documento rapidamente se tornará incrivelmente longo e poderia facilmente ultrapassar o limite de tamanho de 16MB, portanto, a cardinalidade desta relação sugere contra a incorporação. Além disso, as mensagens podem precisar ser acessadas separadamente do estudante, como poderia ser o caso se a página do quadro de mensagens for projetada para mostrar as mensagens mais recentes postadas por estudantes. Isso também sugere que a incorporação não é a melhor escolha para este cenário.
Nota: Você também deve considerar se as mensagens do quadro de mensagens são frequentemente acessadas ao recuperar o documento do estudante. Se não forem, ter todas elas incorporadas dentro desse documento incorrerá em uma penalidade de desempenho ao recuperar e manipular este documento, mesmo quando a lista de mensagens não for usada com frequência. O acesso infrequente de dados relacionados é frequentemente outra pista de que você não deve incorporar documentos.
Agora considere usar referências filhas em vez de incorporar documentos completos como no exemplo anterior. As mensagens individuais seriam armazenadas em uma coleção separada, e o documento do estudante poderia então ter a seguinte estrutura:
Neste exemplo, o campo message_board_messages
agora armazena as referências filhas para todas as mensagens escritas por Sammy. No entanto, mudar a abordagem resolve apenas um dos problemas mencionados anteriormente, pois agora seria possível acessar as mensagens de forma independente. Mas, embora o tamanho do documento do estudante cresça mais lentamente usando a abordagem de referências filhas, a coleção de identificadores de objetos também pode se tornar desordenada, dada a cardinalidade ilimitada desta relação. Afinal, um estudante poderia facilmente escrever milhares de mensagens durante seus quatro anos de estudo.
Nessas situações, uma maneira comum de conectar um objeto a outro é através de referências pai. Diferentemente das referências filhas descritas anteriormente, agora não é o documento do estudante que se refere a mensagens individuais, mas sim uma referência no documento da mensagem apontando para o estudante que a escreveu.
Para usar referências pai, você precisaria modificar o esquema do documento da mensagem para conter uma referência ao estudante que a autorizou:
Observe que o novo campo posted_by
contém o identificador de objeto do documento do estudante. Agora, o documento do estudante não conterá nenhuma informação sobre as mensagens que postou:
Para recuperar a lista de mensagens escritas por um estudante, você faria uma consulta na coleção de mensagens e filtraria contra o campo posted_by
. Tê-las em uma coleção separada torna seguro permitir que a lista de mensagens cresça sem afetar nenhum dos documentos do estudante.
Nota: Ao utilizar referências pai, criar um índice no campo que referencia o documento pai pode aumentar significativamente o desempenho das consultas cada vez que você filtra pelo identificador do documento pai.
Se você modelar uma relação um-para-muitos onde a quantidade de documentos relacionados é ilimitada, independentemente de precisarem ser acessados de forma independente, geralmente é aconselhável armazenar os documentos relacionados separadamente e usar referências pai para conectá-los ao documento pai.
Conclusão
Graças à flexibilidade dos bancos de dados orientados a documentos, determinar a melhor maneira de modelar relacionamentos em bancos de dados de documentos é menos uma ciência exata do que em um banco de dados relacional. Ao ler este artigo, você se familiarizou com a incorporação de documentos e o uso de referências filho e pai para armazenar dados relacionados. Você aprendeu sobre considerar a cardinalidade da relação e evitar arrays ilimitados, bem como levar em conta se o documento será acessado separadamente ou frequentemente.
Essas são apenas algumas diretrizes que podem ajudá-lo a modelar relacionamentos típicos no MongoDB, mas modelar esquemas de banco de dados não é uma solução única para todos. Sempre considere seu aplicativo e como ele usa e atualiza os dados ao projetar o esquema.
Para saber mais sobre o design de esquemas e padrões comuns para armazenar diferentes tipos de dados no MongoDB, incentivamos você a consultar a documentação oficial do MongoDB sobre esse assunto.
Source:
https://www.digitalocean.com/community/tutorials/how-to-design-a-document-schema-in-mongodb