Múltiplas conexões MongoDB em um único aplicativo
Avalie esse Tutorial
O MongoDB, um popular banco de dados NoSQL, é amplamente utilizado em vários aplicativos e cenários. Embora uma única conexão de banco de dados possa atender adequadamente às necessidades de vários projetos, existem cenários específicos e vários casos de uso do mundo real que destacam as vantagens de empregar várias conexões.
Neste artigo, veremos o conceito de estabelecer múltiplas conexões do MongoDB em um único aplicativo Node.js.
No mundo do MongoDB e dos aplicativos baseados em dados, a demanda por múltiplas conexões do MongoDB está aumentando. Vamos explorar por que essa necessidade surge e descobrir casos de uso e exemplos do mundo real em que várias conexões fornecem uma solução vital.
Setores como e-commerce, jogos, serviços financeiros, mídia, entretenimento e Internet das Coisas (IoT) geralmente sofrem com volumes substanciais de dados ou dados de diversas fontes.
Por exemplo, imagine um aplicativo da web que distribui o tráfego uniformemente entre vários servidores MongoDB usando várias conexões ou uma arquitetura de microsserviços em que cada microsserviço acessa o banco de dados por meio de sua conexão dedicada. Talvez em um aplicativo de processamento de dados, várias conexões permitam a recuperação de dados de vários servidores MongoDB simultaneamente. Mesmo um aplicativo de backup pode empregar várias conexões para fazer backup eficiente de dados de vários servidores MongoDB para um único servidor de backup.
Além disso, considere um aplicativo multilocatário em que diferentes locatários ou clientes compartilham o mesmo aplicativo da web, mas exigem bancos de dados separados e isolados. Nesse cenário, cada locatário pode ter sua própria conexão do MongoDB dedicada. Isso garante a separação, a segurança e a personalização de dados para cada locatário enquanto todos operam no mesmo aplicativo. Essa abordagem simplifica o gerenciamento e fornece uma maneira eficiente de dimensionar à medida que novos locatários ingressam na plataforma sem afetar os existentes.
Antes de nos aprofundarmos na implementação prática, vamos apresentar alguns conceitos-chave que serão relevantes nos próximos exemplos de casos de uso. Considere vários casos de uso, como balanceamento de carga, fragmentação, réplicas de leitura, isolamento e tolerância a falhas. Esses conceitos executam um papel crucial em cenários onde múltiplas conexões MongoDB são necessárias para gerenciamento eficiente de dados e otimização de desempenho.
Ao longo deste guia, usaremos Node.js, Express.js e o pacote Mongoose NPM para gerenciar interações com o MongoDB. Antes de prosseguir, certifique-se de que seu ambiente de desenvolvimento esteja pronto e que você tenha essas dependências instaladas.
Se você é iniciante no MongoDB ou nunca configurou o MongoDB, o primeiro passo é configurar uma conta do MongoDB Atlas. Veja instruções passo a passo sobre como fazer isso no artigo Primeiros passos com o Atlas do MongoDB.
Esta publicação usa MongoDB 6.3.2 e Node.js 18.17.1
Se você estiver planejando criar um novo projeto, comece criando um novo diretório para seu projeto. Em seguida, inicie um novo projeto usando o comando
npm init
.Se você já tiver um projeto existente e quiser integrar essas dependências, verifique se o diretório do projeto está aberto. Nesse caso, você só precisa instalar as dependências do Express e do Mongoose, caso ainda não o tenha feito, certificando-se de especificar os números das versões para evitar possíveis conflitos.
1 npm i express@4.18.2 mongoose@7.5.3
Esteja ciente de que o Mongoose não é o driver oficial do MongoDB, mas uma biblioteca popular de modelagem de dados de objetos (ODM) para o MongoDB. Se preferir usar o driver oficial do MongoDB, você pode encontrar a documentação relevante no site oficial do MongoDB.
A próxima etapa é configurar o arquivo de ambiente
.env
, caso ainda não o tenha feito. Definiremos variáveis para as strings de conexão do MongoDB que usaremos ao longo deste artigo. A variável PRIMARY_CONN_STR
é para a string de conexão primária do MongoDB, e a variável SECONDARY_CONN_STR
é para a string de conexão secundária do MongoDB.1 PRIMARY_CONN_STR=mongodb+srv://… 2 SECONDARY_CONN_STR=mongodb+srv://…
Se você for iniciante no MongoDB e precisar de orientação para obter uma string de conexão MongoDB do Atlas, consulte o artigo Obter string de conexão.
Agora, dividiremos o processo de conexão em duas partes: uma para a conexão primária e outra para a conexão secundária.
Agora, vamos começar configurando a conexão primária.
O processo de conexão principal pode ser familiar se você já o tiver implementado em seu aplicativo. Mesmo assim, fornecerei uma explicação detalhada para maior clareza. Os leitores que já estão familiarizados com esse processo podem pular esta seção.
Normalmente, utilizamos o método mongoose.connect() para estabelecer a conexão primária com o MongoDB database para o nosso aplicativo, pois ele gerencia com eficiência um único pool de conexões para todo o aplicativo.
Em um arquivo separado chamado
db.primary.js
, definimos um método de conexão que usaremos em nosso arquivo do aplicativo principal (por exemplo, index.js
). Este método, mostrado abaixo, configura a conexão do MongoDB e gerencia eventos:1 const mongoose = require("mongoose"); 2 3 module.exports = (uri, options = {}) => { 4 // By default, Mongoose skips properties not defined in the schema (strictQuery). Adjust it based on your configuration. 5 mongoose.set('strictQuery', true); 6 7 // Connect to MongoDB 8 mongoose.connect(uri, options) 9 .then() 10 .catch(err => console.error("MongoDB primary connection failed, " + err)); 11 12 // Event handling 13 mongoose.connection.once('open', () => console.info("MongoDB primary connection opened!")); 14 mongoose.connection.on('connected', () => console.info("MongoDB primary connection succeeded!")); 15 mongoose.connection.on('error', (err) => { 16 console.error("MongoDB primary connection failed, " + err); 17 mongoose.disconnect(); 18 }); 19 mongoose.connection.on('disconnected', () => console.info("MongoDB primary connection disconnected!")); 20 21 // Graceful exit 22 process.on('SIGINT', () => { 23 mongoose.connection.close().then(() => { 24 console.info("Mongoose primary connection disconnected through app termination!"); 25 process.exit(0); 26 }); 27 }); 28 }
A próxima etapa é criar esquemas para realizar operações em seu aplicativo. Escreveremos o esquema em um arquivo separado chamado
product.schema.js
e o exportaremos. Vamos dar um exemplo de esquema para produtos em um aplicativo de lojas:1 const mongoose = require("mongoose"); 2 3 module.exports = (options = {}) => { 4 // Schema for Product 5 return new mongoose.Schema( 6 { 7 store: { 8 _id: mongoose.Types.ObjectId, // Reference-id to the store collection 9 name: String 10 }, 11 name: String 12 // add required properties 13 }, 14 options 15 ); 16 }
Agora, vamos importar o arquivo
db.primary.js
em nosso arquivo principal (por exemplo, index.js
) e usar o método definido lá para estabelecer a conexão primária do MongoDB. Você também pode passar um objeto de opções de conexão opcional, se necessário.Depois de configurar a conexão primária do MongoDB, você importa o arquivo
product.schema.js
para acessar o esquema do produto. Isso permite criar um modelo e executar operações relacionadas a produtos em seu aplicativo:1 // Primary Connection (Change the variable name as per your .env configuration!) 2 // Establish the primary MongoDB connection using the connection string variable declared in the Prerequisites section. 3 require("./db.primary.js")(process.env.PRIMARY_CONN_STR, { 4 // (optional) connection options 5 }); 6 7 // Import Product Schema 8 const productSchema = require("./product.schema.js")({ 9 collection: "products", 10 // Pass configuration options if needed 11 }); 12 13 // Create Model 14 const ProductModel = mongoose.model("Product", productSchema); 15 16 // Execute Your Operations Using ProductModel Object 17 (async function () { 18 let product = await ProductModel.findOne(); 19 console.log(product); 20 })();
Agora, vamos configurar uma conexão secundária ou segunda conexão do MongoDB para cenários em que seu aplicativo exige múltiplas conexões do MongoDB.
Dependendo dos requisitos do seu aplicativo, você pode configurar conexões secundárias do MongoDB para vários casos de uso. Mas, antes disso, criaremos um código de conexão em um arquivo
db.secondary.js
, usando especificamente o método mongoose.createConnection(). Esse método nos permite estabelecer pools de conexões separados, cada um personalizado para um caso de uso ou padrão de acesso a dados específicos, diferentemente do método mongoose.connect()
que usamos anteriormente para a conexão primária do MongoDB:1 const mongoose = require("mongoose"); 2 3 module.exports = (uri, options = {}) => { 4 // Connect to MongoDB 5 const db = mongoose.createConnection(uri, options); 6 7 // By default, Mongoose skips properties not defined in the schema (strictQuery). Adjust it based on your configuration. 8 db.set('strictQuery', true); 9 10 // Event handling 11 db.once('open', () => console.info("MongoDB secondary connection opened!")); 12 db.on('connected', () => console.info(`MongoDB secondary connection succeeded!`)); 13 db.on('error', (err) => { 14 console.error(`MongoDB secondary connection failed, ` + err); 15 db.close(); 16 }); 17 db.on('disconnected', () => console.info(`MongoDB secondary connection disconnected!`)); 18 19 // Graceful exit 20 process.on('SIGINT', () => { 21 db.close().then(() => { 22 console.info(`Mongoose secondary connection disconnected through app termination!`); 23 process.exit(0); 24 }); 25 }); 26 27 // Export db object 28 return db; 29 }
Agora, vamos importar o arquivo
db.secondary.js
em nosso arquivo principal (por exemplo, index.js
), criar o objeto de conexão com uma variável chamada db
e usar o método definido lá para estabelecer a conexão secundária do MongoDB. Você também pode passar um objeto de opções de conexão opcional, se necessário:1 // Secondary Connection (Change the variable name as per your .env configuration!) 2 // Establish the secondary MongoDB connection using the connection string variable declared in the Prerequisites section. 3 const db = require("./db.secondary.js")(process.env.SECONDARY_CONN_STR, { 4 // (optional) connection options 5 });
Agora que já temos a conexão pronta, você pode usar esse objeto
db
para criar um modelo. Exploramos diferentes cenários e exemplos para ajudar você a escolher a configuração que melhor se alinha às suas necessidades específicas de acesso e gerenciamento de dados:Você pode optar por usar o mesmo arquivo de esquema
product.schema.js
que foi empregado na conexão primária. Isso é adequado para cenários em que ambas as conexões operam no mesmo modelo de dados.Importe o arquivo
product.schema.js
para acessar o esquema do produto. Isso permite criar um modelo usando o objetodb
e executar operações relacionadas a produtos em seu aplicativo:1 // Import Product Schema 2 const secondaryProductSchema = require("./product.schema.js")({ 3 collection: "products", 4 // Pass configuration options if needed 5 }); 6 7 // Create Model 8 const SecondaryProductModel = db.model("Product", secondaryProductSchema); 9 10 // Execute Your Operations Using SecondaryProductModel Object 11 (async function () { 12 let product = await SecondaryProductModel.findOne(); 13 console.log(product); 14 })();
Para ver um exemplo de código prático e os recursos disponíveis para usar o esquema existente de uma conexão de banco de dados primária em uma conexão secundária do MongoDB em seu projeto, visite o repositório do GitHub.
Ao trabalhar com várias conexões do MongoDB, é essencial ter a flexibilidade de adaptar seu esquema com base em casos de uso específicos. Embora a conexão primária possa exigir um esquema rigoroso com validação para garantir a integridade dos dados, há cenários em que uma conexão secundária tem uma finalidade diferente. Por exemplo, uma conexão secundária pode armazenar dados para análise em um servidor de arquivamento, com requisitos de esquema variados, orientados por casos de uso anteriores. Nesta seção, exploraremos como configurar a flexibilidade de esquema para sua conexão secundária, permitindo que você atenda às necessidades distintas de seu aplicativo.
Se preferir ter flexibilidade de esquema no mongoose, você pode passar a propriedade
strict: false
nas opções ao configurar o esquema para a conexão secundária. Isso permite trabalhar com dados que não aderem estritamente ao esquema.Importe o arquivo
product.schema.js
para acessar o esquema do produto. Isso permite criar um modelo usando o objetodb
e executar operações relacionadas a produtos em seu aplicativo:1 // Import Product Schema 2 const secondaryProductSchema = require("./product.schema.js")({ 3 collection: "products", 4 strict: false 5 // Pass configuration options if needed 6 }); 7 8 // Create Model 9 const SecondaryProductModel = db.model("Product", secondaryProductSchema); 10 11 // Execute Your Operations Using SecondaryProductModel Object 12 (async function () { 13 let product = await SecondaryProductModel.findOne(); 14 console.log(product); 15 })();
Para ver um exemplo de código prático e os recursos disponíveis para definir a flexibilidade do esquema em uma conexão secundária do MongoDB em seu projeto, visite o repositório do GitHub.
Na configuração do banco de dados do seu aplicativo, você pode alternar perfeitamente entre diferentes bancos de dados usando o método db.useDb(). Esse método permite criar um novo objeto de conexão associado a um banco de dados específico e também compartilha o mesmo pool de conexões.
Essa abordagem permite gerenciar com eficiência vários banco de dados em seu aplicativo, usando uma única conexão e mantendo contextos de dados distintos para cada banco de dados.
Importe o arquivo
product.schema.js
para acessar o esquema do produto. Isso permite que você crie um modelo usando o objeto db
e execute operações relacionadas a produtos em seu aplicativo.Agora, para fornecer um exemplo em que uma loja pode ter seu próprio banco de dados contendo usuários e produtos, você pode incluir o seguinte cenário.
Exemplo de caso de uso: armazenamento com banco de dados separado
Imagine que você esteja desenvolvendo uma plataforma de e-commerce em que várias lojas operam de forma independente. Cada loja tem seu banco de dados para gerenciar seus produtos. Nesse cenário, você pode usar o método
db.useDb()
para alternar entre diferentes bancos de dados de armazenamento enquanto mantém um pool de conexões compartilhado:1 // Import Product Schema 2 const secondaryProductSchema = require("./product.schema.js")({ 3 collection: "products", 4 // strict: false // that doesn't adhere strictly to the schema! 5 // Pass configuration options if needed 6 }); 7 8 // Create a connection for 'Store A' 9 const storeA = db.useDb('StoreA'); 10 11 // Create Model 12 const SecondaryStoreAProductModel = storeA.model("Product", secondaryProductSchema); 13 14 // Execute Your Operations Using SecondaryStoreAProductModel Object 15 (async function () { 16 let product = await SecondaryStoreAProductModel.findOne(); 17 console.log(product); 18 })(); 19 20 // Create a connection for 'Store B' 21 const storeB = db.useDb('StoreB'); 22 23 // Create Model 24 const SecondaryStoreBProductModel = storeB.model("Product", secondaryProductSchema); 25 26 // Execute Your Operations Using SecondaryStoreBProductModel Object 27 (async function () { 28 let product = await SecondaryStoreBProductModel.findOne(); 29 console.log(product); 30 })();
Neste exemplo, conexões de banco de dados separadas foram estabelecidas para
Store A
e Store B
, cada uma contendo seus dados do produto. Essa abordagem fornece uma separação clara de dados e, ao mesmo tempo, utiliza de forma eficiente um único pool de conexões compartilhado para todas as lojas, aprimorando o gerenciamento de dados em uma plataforma de e-commerce de várias lojas.Na seção anterior, demonstramos uma abordagem estática em que as conexões eram explicitamente criadas para cada armazenamento, e cada conexão era nomeada de acordo com isso (por exemplo,
StoreA
, StoreB
).Para introduzir uma abordagem dinâmica, você pode criar uma função que aceite o ID ou nome de uma loja como parâmetro e retorne um objeto de conexão. Essa função dinâmica permite alternar entre diferentes armazenamentos, fornecendo seus identificadores, e reutiliza eficientemente as conexões existentes quando possível.
1 // Function to get connection object for particular store's database 2 function getStoreConnection(storeId) { 3 return db.useDb("Store"+storeId, { useCache: true }); 4 } 5 6 // Create a connection for 'Store A' 7 const store = getStoreConnection("A"); 8 9 // Create Model 10 const SecondaryStoreProductModel = store.model("Product", secondaryProductSchema); 11 12 // Execute Your Operations Using SecondaryStoreProductModel Object 13 (async function () { 14 let product = await SecondaryStoreProductModel.findOne(); 15 console.log(product); 16 })();
Na abordagem dinâmica, as instâncias de conexão são criadas e armazenadas em cache conforme necessário, eliminando a necessidade de gerenciar manualmente conexões separadas para cada loja. Essa abordagem aumenta a flexibilidade e a eficiência de recursos em cenários em que você precisa trabalhar com várias lojas em seu aplicativo.
Ao explorar esses exemplos, cobrimos uma série de cenários para o gerenciamento de vários bancos de dados na mesma conexão, oferecendo a flexibilidade necessária para adaptar a configuração do banco de dados às necessidades específicas do seu aplicativo. Agora você está preparado para gerenciar com eficiência contextos de dados distintos para vários casos de uso em seu aplicativo.
Para ver um exemplo de código prático e os recursos disponíveis para alternar bancos de dados dentro da mesma conexão em uma conexão MongoDB secundária em seu projeto, visite o repositório do GitHub.
Na busca por uma configuração do MongoDB robusta e eficiente em seu aplicativo Node.js, recomendamos as seguintes práticas. Essas diretrizes servem como base para uma implementação confiável, e eu sugiro considerá-las e implementá-las:
- Pool de conexões: aproveite ao máximo o pool de conexões para gerenciar com eficiência as conexões do MongoDB, permitindo a reutilização de conexões e reduzindo a sobrecarga. Leia mais sobre pool de conexões.
- Tratamento de erros: mecanismos robustos de tratamento de erros, registro abrangente e planos de contingência garantem a confiabilidade da sua configuração do MongoDB diante de problemas inesperados.
- Segurança: priorize a segurança de dados com práticas de autenticação, autorização e comunicação segura, especialmente ao lidar com informações confidenciais. Leia mais sobre a Segurança do MongoDB.
- Escalabilidade: planeje a escalabilidade desde o início, considerando estratégias de dimensionamento horizontal e vertical para acomodar o crescimento do seu aplicativo.
- Testes: Ttestes abrangentes em vários cenários, como failover, alta carga e restrições de recursos, validam a resiliência e o desempenho de sua configuração de conexão múltipla com o MongoDB.
Aproveitar várias conexões do MongoDB em um aplicativo Node.js abre um mundo de possibilidades para diversos casos de uso, de e-commerce a sistemas multilocatários. Se você precisa aprimorar a separação de dados, escalar seu aplicativo com eficiência ou acomodar diferentes padrões de acesso aos dados, essas técnicas permitem adaptar a configuração do banco de dados às necessidades exclusivas do seu projeto. Com o conhecimento adquirido neste guia, você está bem preparado para gerenciar vários contextos de dados em um único aplicativo, garantindo interações robustas, flexíveis e eficientes com o MongoDB.
- Documentação do Mongoose: para uma compreensão profunda das conexões do Mongoose, navegue na documentação oficial do Mongoose.
- Repositório Github: para mergulhar na implementação completa de várias conexões do MongoDB em um aplicativo Node.js que realizamos acima, visite o repositório Github. Sinta-se à vontade para clonar o repositório e experimentar diferentes casos de uso em seus projetos.
Se você tiver alguma dúvida ou feedback, confira os fóruns da MongoDB Community e deixe sua opinião.