Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
Javachevron-right

Projetos de coleção única no MongoDB com dados Spring (Parte 1)

Graeme Robinson10 min read • Published Oct 13, 2022 • Updated Sep 09, 2024
Java
Ícone do FacebookÍcone do Twitterícone do linkedin
Design de esquema
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Bancos de dados NoSQL baseados em documentos modernos, como o MongoDB, oferecem vantagens em relação aos relational database tradicionais para muitos tipos de aplicativos. Um dos principais benefícios são os modelos de dados que evitam a necessidade de dados normalizados espalhados por várias tabelas que exigem operações de junção que são computacionalmente caras e difíceis de dimensionar horizontalmente.
Na primeira parte desta série, discutiremos projetos de coleção única — um dos padrões de projeto usados para realizar essas vantagens no MongoDB. Na parte 2, forneceremos exemplos de como o padrão de coleção única pode ser utilizado em aplicativos Java usando o Spring Data MongoDB.

O aplicativo de controle de tráfego aéreo ADSB

Nesta postagem do blog, discutimos um projeto de banco de dados para coletar e analisar dados de Transmissão de Vigilância Dependente Automática (ADSB) transmitidos por aeronaves. O ADSB é um componente de uma grande modernização mundial dos sistemas de controle de tráfego aéreo que se afasta da dependência do radar (que é caro de manter e tem alcance limitado) para rastrear o movimento da aeronave e, em vez disso, faz com que as próprias aeronaves transmitam sua localização, velocidade, altitude e direção de viagem, tudo baseado em Sistemas Globais de Navegação por Satélite aprovados, como GPS, GLONASS, Galileu e BeiDou. Encontre mais informações sobre o ADSB.
Vários dispositivos de consumo estão disponíveis para receber transmissões ADSB de aeronaves próximas. Eles são usados por pilotos de aeronaves leves para alimentar dados em aplicativos de navegação baseados em tablets e smartphones, como o Foreflight. Isso fornece um nível de consciência situacional e segurança em relação à localização do tráfego de voos nas proximidades que anteriormente simplesmente não estava disponível nem mesmo para pilotos de companhias aéreas comerciais. Além disso, iniciativas de rastreamento de aeronaves baseadas na web, como a Opensky Network, dependem de dados ADSB de origem comunitária para construir seus bancos de dados usados para vários projetos de pesquisa.
Captura de tela do aplicativo Foreflight em execução em um iPhone Enquanto a maioria dos receptores ADSB é vendida na faixa de preço de centenas de dólares, o excelente projeto de código aberto Stratux permite que um sistema receptor completo seja construído usando um Raspberry Pi e rádios definidos por software USB (SDRs) baratos. Um sistema completo pode ser construído a partir de peças totalizando cerca de $200 (1). Documentação de um receptor Mapset ADSB baseado em Raspberry Pi O receptor Stratux transmite dados para aplicativos de escuta por meio de uma conexão TCP/IP bruta com mensagens que aderem à especificaçãoGDL90 projetada e mantida pela Garmin ou como mensagens JSON enviadas aos assinantes de uma conexão websocket. Neste exercício, simularemos o recebimento de mensagens de um receptor Stratux -um receptor funcional não é um pré-requisito para concluir os exercícios. O banco de dados que construiremos rastreará as aeronaves observadas, as companhias aéreas às quais pertencem e os relatórios de posição individuais do ADSB coletados por nosso receptor.
Em um sistema tradicional baseado em RDBMS, podemos escolher um modelo de dados normalizado semelhante a este: Diagrama ERD para um sistema de rastreamento de voo Cada registro na tabela de companhias aéreas pode ser unido a zero ou mais registros de aeronaves, e cada registro de aeronave pode ser unido a zero ou mais relatórios de posição ADSB. Embora esse modelo ofereça um grau de flexibilidade em termos de consulta, as consultas que se unem entre tabelas são computacionalmente intensivas e difíceis de dimensionar horizontalmente. Em particular, considere que mais de 3000 voos comerciais são operados por dia por aeroportos na área da cidade de Nova York e que cada um desses voos está transmitindo um novo relatório de posição do ADSB a cada segundo. Com as transmissões ADSB para um voo sendo captadas pelo receptor por uma média de 15 minutos até que a aeronave saia do alcance, um receptor ADSB sozinho em Nova York pode estar alimentando 2.5 milhões de relatórios de posição por dia no sistema. Com uma rede de receptores ADSB posicionados nos principais hubs dos EUA, a possibilidade de precisar escalar pode crescer rapidamente.
O MongoDB foi projetado desde o início para ser fácil de escalar horizontalmente. No entanto, para fazer isso, os princípios e padrões de design corretos devem ser empregados, um dos quais é evitar junções desnecessárias. Em nosso caso, utilizaremos o modelo de dados do documento, collections polimórficase o padrão de design de collection única. E embora seja prática comum no design de relational database começar normalizando os dados antes de considerar os padrões de acesso, com bancos de dados centrados em documentos, como o MongoDB, você deve sempre começar considerando os padrões de acesso para seus dados e trabalhar a partir daí, usando o princípio orientador de que os dados acessados juntos devem ser armazenados juntos
No MongoDB, os dados são armazenados no JSON (2) como documentos, organizados em coleções. Em termos de banco de dados relacional, um documento é análogo a um registro, enquanto uma coleção é análoga a uma tabela. No entanto, existem algumas diferenças importantes das quais você deve estar ciente.
Um documento no MongoDB pode ser hierárquico, pois o valor de qualquer atributo (coluna em termos relacionais) em um documento pode ser ele próprio um documento ou uma matriz de valores ou documentos. Isso permite que os dados sejam armazenados em um único documento dentro de uma collection de maneiras que os designs de relational database tabulares não suportam e que exigiriam que os dados fossem armazenados em várias tabelas e acessados por meio de uniões. Considere que nossa companhia aérea ligará uma aeronave para muitos e entre aeronaves e a posição ADSB reportará relações um-para-muitos. Em nosso modelo relacional, isso requer três tabelas unidas usando relacionamentos de chave primária- estrangeira . No MongoDB, isso pode ser representado por documentos da companhia aérea, com suas aeronaves associadas incorporadas no mesmo documento e os relatórios de posição do ADSB para cada aeronave posteriormente incorporados, todos armazenados em uma única collection. Esses documentos podem ter a seguinte aparência:
1{
2  "_id": {
3    "$oid": "62abdd534e973de2fcbdc10d"
4  },
5  "airlineName": "Delta Air Lines",
6  "airlineIcao": "DAL",
7  ...
8
9  "aircraft": [
10    {
11      "icaoNumber": "a36f7e",
12  "tailNumber": "N320NB",
13      ...
14 "positionReports": [
15        {
16          "msgNum": "1",
17          "altitude": 38825,
18          ...
19      "geoPoint": {
20            "type": "Point",
21            "coordinates": [
22
23              -4.776722,
24              55.991776
25
26            ]
27          },
28
29        },
30
31        {
32           "msgNum": "2",
33           ...
34        },
35        {
36           "msgNum": "3",
37
38           ...
39        }
40      ]
41    },
42
43    {
44      "icaoNumber": "a93d7c",
45  ...
46    },
47    {
48  "icaoNumber": "ab8379",
49  ...
50    },
51  ]
52}
Ao incorporar as informações da aeronave de cada companhia aérea em seu próprio documento, todas armazenadas em uma única coleção, podemos recuperar as informações de uma companhia aérea e de todas as suas aeronaves usando uma única consulta e sem junções:
1db.airlines.find({"airlineName": "Delta Air Lines"}
Documentos incorporados e hierárquicos fornecem uma grande flexibilidade em nosso design de dados e são consistentes com nosso princípio orientador de que osdados acessados juntos devem ser armazenados juntos. No entanto, há algumas coisas a serem observadas:
  • Para algumas companhias aéreas, o número de documentos de naves incorporados pode ficar grande. Isso seria composto pelo número de relatórios de posição ADDB incorporados em cada documento de avião associado. Em geral, arrays grandes e ilimitadas são considerados um antipadrão no MongoDB, pois podem levar a documentos excessivamente dimensionados com um impacto correspondente nas operações de atualização e na recuperação de dados.
  • Pode ser necessário acessar os dados de uma companhia aérea ou aeronave individual independentemente dos dados da aeronave correspondente ou das informações relacionadas a outras aeronaves da frota da companhia aérea. Embora a estrutura de agregação de query do MongoDB permita essa modelagem e projeção dos dados retornados por uma query para fazer isso, ela adicionaria sobrecarga de processamento ao realizar essas queries. Como alternativa, os dados necessários poderiam ser filtrados dos retornos da consulta em nosso aplicativo, mas isso poderia levar a transmissões desnecessárias de grandes volumes de dados.
  • Algumas aeronaves podem ser operadas de forma privada e não estar associadas a uma companhia aérea.
Uma abordagem para lidar com esses problemas seria separar os dados do relatório de posição da companhia aérea, da avião e do ADSB em documentos separados armazenados em três collections diferentes com referências cruzadas apropriadas (chaves primárias/eternas). Em alguns casos, essa pode ser a abordagem correta. No entanto, ele tem o custo de manter collections e índices adicionais e pode exigir o uso de junções ($lookup estágios em um pipeline de agregação do MongoDB) ao recuperar dados. Para alguns de nossos padrões de acesso, esse design violaria nosso princípio orientador de que os dados acessados juntos devem ser armazenados juntos. Além disso, à medida que a quantidade de dados em um aplicativo cresce e a necessidade de dimensionamento por meio da fragmentação de dados começa a ser considerada, ter dados relacionados separados em várias coleções pode complicar a manutenção de dados nos fragmentos.
Outra opção seria considerar o uso de 
o padrão de subconjunto
 o que limita o número de documentos incorporados que mantemos de acordo com um algoritmo (geralmente recebidos/acessados mais recentemente ou acessados com mais frequência), com os documentos restantes armazenados em coleções separadas. Isso nos permite controlar o tamanho de nossos documentos hierárquicos e, em muitas cargas de trabalho, cobrir nossos padrões de acesso e recuperação de dados com uma única consulta em uma única coleção. No entanto, para nosso caso de uso de dados de companhias aéreas, podemos descobrir que a frequência com que estamos solicitando todas as aeronaves de uma determinada companhia aérea ou todos os relatórios de posição de uma aeronave (dos quais pode haver muitos milhares), o padrão de subconjunto ainda pode levar a muitas consultas que exigem junções.
Uma outra solução, e a abordagem que adotaremos neste artigo, é utilizar outro recurso do MongoDB: collections polimórficas. Coleções polimórficas referem-se à capacidade das coleções de armazenar documentos de vários tipos. Ao contrário das tabelas relacionais, onde as colunas de cada tabela são predefinidas, uma coleção no MongoDB pode conter documentos de qualquer design, e o único requisito é que cada documento contenha um campo "_id " contendo um identificador exclusivo. Essa capacidade levou alguns observadores a descrever o MongoDB como sem esquema. No entanto, é mais correto descrever MongoDB como "schema-optional. " Você pode definir restrições no design de documentos que são aceitos por uma coleção usando JSON schema, mas isso é opcional e fica a critério dos desenvolvedores do aplicativo. Por padrão, nenhuma restrição é imposta. É considerado prática recomendada armazenar apenas documentos que estejam de alguma forma relacionados e/ou serão recuperados em uma única operação dentro da mesma coleção mas, novamente, isso fica a critério dos desenvolvedores. 
Utilizando a collection polimórfica em nosso exemplo de aerodata, separamos os dados do relatório de posição da companhia aérea, da aeronave e do ADSB em documentos separados, mas os armazenamos em uma única collection. Com essa abordagem, os documentos em nossa collection podem ter a seguinte aparência:
1{
2 "_id": "DAL",
3 "airlineName": "Delta Air Lines",
4 ...
5 "recordType": 1
6},
7{
8 "_id": "DAL_a93d7c",
9 "tailNumber": "N695CA",
10 "manufacturer": "Bombardier Inc",
11 "model": "CL-600-2D24",
12 "recordType": 2
13},
14{
15 "_id": "DAL_ab8379",
16 "tailNumber": "N8409N",
17 "manufacturer": "Bombardier Inc",
18 "model": "CL-600-2B19",
19 "recordType": 2
20},
21{
22 "_id": "DAL_a36f7e",
23 "tailNumber": "N8409N",
24 "manufacturer": "Airbus Industrie",
25 "model": "A319-114",
26 "recordType": 2
27},
28{
29 "_id": "DAL_a36f7e_1",
30 "altitude": 38825,
31 . . .
32 "geoPoint": {
33 "type": "Point",
34 "coordinates": [
35 -4.776722,
36 55.991776
37 ]
38 },
39 "recordType": 3
40},
41{
42 "_id": "DAL_a36f7e_2",
43 "altitude": 38875,
44 ...
45 "geoPoint": {
46 "type": "Point",
47 "coordinates": [
48 -4.781466,
49 55.994843
50 ]
51 },
52 "recordType": 3
53},
54{
55 "_id": "DAL_a36f7e_3",
56 "altitude": 38892,
57 ...
58 "geoPoint": {
59 "type": "Point",
60 "coordinates": [
61 -4.783344,
62 55.99606
63 ]
64 },
65 "recordType": 3
66}
Há algumas coisas a serem observadas aqui. Em primeiro lugar, com os relatórios de posição da companhia aérea, da aeronave e do ADSB separados em documentos individuais, em vez de incorporados uns aos outros, podemos consultar e retornar os diferentes tipos de documentos individualmente ou em combinação, conforme necessário.
Em segundo lugar, utilizamos um formato personalizado para o campo “_id” em cada documento. Embora o campo “_id” seja sempre obrigatório no MongoDB, o formato do valor armazenado no campo pode ser qualquer coisa, desde que seja exclusivo nessa collection. Por padrão, se nenhum valor for fornecido, o MongoDB atribuirá um valor objectID ao campo. No entanto, não há nada que nos impeça de usar qualquer valor que desejemos, desde que haja cuidado para garantir que cada valor usado seja único. Considerando que o MongoDB sempre manterá um índice no campo “_id”, faz sentido que usemos um valor no campo que tenha algum valor para nosso aplicativo. No nosso caso, os valores são usados para representar a hierarquia em nossos dados. Os campos “_id” do documento da companhia aérea contêm o código ICAO (Organização Internacional da Aviação Civil) exclusivo da companhia aérea. Os campos “_id” do documento da aeronave começam com o código ICAO da companhia aérea proprietária, seguido por um sublinhado, seguido pelo código ICAO exclusivo da aeronave. Finalmente, os campos “_id” do documento do relatório de posição do ADSB começam com o código ICAO da companhia aérea, um sublinhado, depois o código ICAO da aeronave, depois um segundo sublinhado e, finalmente, um número de mensagem incremental. 
Embora pudéssemos ter armazenado os códigos ICAO das companhias aéreas e aeronaves e os números de mensagens ADSB em seus próprios campos para apoiar nossas consultas, e de certa forma fazer isso seria uma abordagem mais simples, teríamos que criar e manter índices adicionais em nossa coleção em relação a cada campo. Sobrecarregar os valores no campo "_id" da maneira que temos evita a necessidade desses índices adicionais.
Por último, adicionamos um campo auxiliar chamado recordType a cada documento para auxiliar na filtragem de pesquisas. Os documentos de companhias aéreas têm um valor recordType de 1, os documentos de avião têm um valor recordType de 2 e os documentos de relatório de posição ADSB têm um valor recordType de 3. Para manter o desempenho da query, o campopositionType deve ser indexado.
Com essas mudanças em vigor, e supondo que tenhamos colocado todos os nossos documentos em uma coleção chamada “aerodata”, agora podemos realizar a seguinte série de consultas:
Recuperar todos os documentos relacionados à Delta Air Lines:
1db.aerodata.find({"_id": /^DAL/})
Recupere o documento da companhia aérea da Delta Air Lines por conta própria:
1db.aerodata.find({"_id": "DAL"})
Recupere todos os documentos de naves da Frota da Delta Air Lines:
1db.aerodata.find({"_id": /^DAL_/, "recordType": 2})
Recupere o documento da avião para o Airbus A319 com o código ICAO "a36f7e" por conta própria:
1db.aerodata.find({"_id": "DAL_a36f7e", "recordType": 2})
Recupere todos os documentos do relatório de posição ADSB para o Airbus A319 com o código ICAO "a36f7e":
1db.aerodata.find({"_id": /^DAL_a36f7e/, "recordType": 3})
Em cada caso, podemos recuperar os dados de que precisamos com uma única operação de consulta (exigindo uma única viagem de ida e volta ao banco de dados) em uma única coleção (e, portanto, sem junções) — mesmo nos casos em que estamos retornando vários documentos de tipos diferentes. Observe o uso de expressões regulares em algumas das queries. Em cada caso, nosso padrão de pesquisa é ancorado no início do valor do campo que está sendo pesquisado usando o símbolo de hat "^ ". Isso é importante ao realizar uma pesquisa de expressão regular, pois o MongoDB só pode utilizar um índice no campo que está sendo pesquisado se o padrão de pesquisa estiver ancorado no início do campo.
A seguinte pesquisa utilizará o índice no campo “_id:
1db.aerodata.find({"_id": /^DAL/})
A pesquisa a seguir não poderá utilizar o índice no campo "_id " e, em vez disso, executará uma verificação completa da collection:
1db.aerodata.find({"_id": /DAL/})
Nesta primeira parte da nossa publicação de duas partes, vimos como os projetos polimórficos de coleção única no MongoDB podem fornecer toda a flexibilidade de consulta dos projetos relacionais normalizados e, ao mesmo tempo, evitar antipadrões, como matrizes ilimitadas e junções desnecessárias. Isso torna as coleções resultantes altamente eficientes do ponto de vista de pesquisar e passíveis de dimensionamento horizontal. Na Parte 2, mostraremos como podemos trabalhar com esses projetos usando o Spring Data MongoDB em aplicativos Java.
O exemplo de código-fonte usado nesta série está disponível no Github.
(1) Em outubro de 2022, os problemas da cadeia de suprimentos da era pandêmica afetaram a disponibilidade e o custo do Raspberry Pi. No entanto, para qualquer pessoa interessada em construir seu próprio receptor Stratux, a seguinte lista de peças permitirá que um sistema básico seja montado:
(2) O MongoDB armazena dados usando BSON - uma forma binária de JSON com suporte para tipos de dados adicionais não suportados pelo JSON. Obtenha mais informações sobre a especificação BSON.

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Início rápido

Java - Mapeamento de POJOs


Mar 01, 2024 | 5 min read
Início rápido

Introdução ao Minecraft e ao MongoDB


Jul 15, 2024 | 4 min read
Início rápido

Introdução ao MongoDB e Helidon


Nov 12, 2024 | 6 min read
Início rápido

Java - Change Streams


Oct 01, 2024 | 10 min read
Sumário
  • O aplicativo de controle de tráfego aéreo ADSB