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
JavaScriptchevron-right

Rastreamento de localização em tempo real com Change Streams e Socket.io

Ashiq Sultan8 min read • Published Feb 09, 2023 • Updated Aug 13, 2024
Node.jsMongoDBFluxos de alteraçõesJavaScript
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Neste artigo, você aprenderá como usar MongoDB Change Streams e Socket.io para criar um aplicativo de rastreamento de localização em tempo real. Para demonstrar isso, criaremos um serviço local de entrega de pacotes.
Os change streams são usados para detectar atualizações de documentos, como localização e status de remessa, e o Socket.io é usado para transmitir essas atualizações para os clientes conectados. Um servidor Express.js será executado em segundo plano para criar e manter os websockets.
Este artigo destacará as partes importantes deste projeto de demonstração, mas você pode encontrar o código completo, juntamente com instruções de configuração, no Github.
visualização das atualizações de localização no aplicativo do usuário quando o simulador de motorista é atualizado

Conecte o Express ao MongoDB Atlas

Conectar Express.js ao MongoDB requer o uso do driver MongoDB, que pode ser instalado como um pacote npm. Neste projeto, usei o MongoDB Atlas e a camada gratuita para criar um cluster. Você pode criar seu próprio cluster gratuito e gerar a connection string a partir do dashboard do Atlas.
Implementei um padrão singleton para conexão com o MongoDB para manter uma conexão única em todo o aplicativo.
O código define uma db variável singleton que armazena a instância do MongoClient após a primeira conexão bem-sucedida com o banco de dados MongoDB.O dbConnect() é uma função assíncrona que retorna a instância do MongoClient. Ele primeiro verifica se a variável db já foi inicializada e a retorna se tiver sido. Caso contrário, ele criará uma nova instância MongoClient e a retornará. A função dbConnecté exportada como exportação padrão, permitindo que outros módulos a usem.
1// dbClient.ts
2import { MongoClient } from 'mongodb';
3const uri = process.env.MONGODB_CONNECTION_STRING;
4let db: MongoClient;
5const dbConnect = async (): Promise<MongoClient> => {
6    try {
7        if (db) {
8            return db;
9        }
10        console.log('Connecting to MongoDB...');
11        const client = new MongoClient(uri);
12        await client.connect();
13        db = client;
14        console.log('Connected to db');
15        return db;
16    } catch (error) {
17        console.error('Error connecting to MongoDB', error);
18        throw error;
19    }
20};
21export default dbConnect;
Agora podemos chamar a função dbConnect no arquivoserver.ts ou qualquer outro arquivo que sirva como ponto de entrada para seu aplicativo.
1// server.ts
2import dbClient from './dbClient';
3server.listen(5000, async () => {
4    try {
5        await dbClient();
6    } catch (error) {
7        console.error(error);
8    }
9});
Agora temos nosso servidor Express conectado ao MongoDB. Com a configuração básica pronta, podemos continuar a incorporar change stream e socket.io em nosso aplicativo.

Fluxos de alterações

O MongoDB Change Stream é um recurso poderoso que permite ouvir alterações em sua collection do MongoDB em tempo real. Os change stream fornecem um mecanismo semelhante a uma notificação de alteração que permite que você seja notificado de quaisquer alterações em seus dados à medida que elas acontecem.
Para usar change streams, use a funçãowatch() do driver do MongoDB. Aqui está um exemplo simples de como você usaria change streams em uma coleção.
1const changeStream = collection.watch()
2changeStream.on('change', (event) => {
3// your logic
4})
A função de retorno de chamada será executada toda vez que um documento for adicionado, excluído ou atualizado na collection monitorada.

Salas Socket.IO e Socket.IO

Socket.IO é uma biblioteca JavaScript popular. Ele permite a comunicação em tempo real entre o servidor e o cliente, sendo ideal para aplicativos que exigem atualizações em tempo real e transmissão de dados. Em nosso aplicativo, ele é usado para transmitir atualizações de localização e status de remessa para os clientes conectados em tempo real.
Um dos principais recursos do Socket.IO é a capacidade de criar “salas”. As salas são uma maneira de segmentar conexões e permitir que você transmita mensagens para grupos específicos de clientes. Em nosso aplicativo, as salas são usadas para garantir que as atualizações de localização e status da remessa sejam transmitidas apenas para os clientes que estão rastreando aquele pacote ou motorista específico.
O código para incluir o Socket.IO e seus manipuladores pode ser encontrado nos arquivos src/server.ts e src/socketHandler.ts
Estamos definindo todos os eventos Socket.IO dentro do arquivosocketHandler.tspara que o código relacionado ao soquete fique separado do restante do aplicativo. Abaixo está um exemplo para implementar os eventos básicos de conexão e desconexão do Socket.IO no Node.js.
1// socketHandler.ts
2const socketHandler = (io: Server) => {
3  io.on('connection', (socket: any) => {
4    console.log('A user connected');
5    socket.on('disconnect', () => {
6      console.log('A user disconnected');
7    });
8  });
9};
10export default socketHandler;
Agora podemos integrar a função socketHandler ao nosso server.ts file (o ponto de partida do nosso aplicativo) importando-a e chamando-a assim que o servidor começar a escutar.
1// server.ts
2import app from './app'; // Express app
3import http from 'http';
4import { Server } from 'socket.io';
5const server = http.createServer(app);
6const io = new Server(server);
7server.listen(5000, async () => {
8  try {
9    socketHandler(io);
10  } catch (error) {
11    console.error(error);
12  }
13});
Agora temos a configuração do Socket.IO com nosso aplicativo Express. Na próxima seção, vamos ver como os dados de localização são armazenados e atualizados.

Armazenando dados de localização

O MongoDB tem suporte integrado para armazenar dados de localização como GeoJSON, o que permite a query e indexação eficientes de dados espaciais. Em nosso aplicativo, a localização do motorista é armazenada no MongoDB como um ponto GeoJSON.
Para simular o movimento do motorista, no front-end, há uma opção para fazer login como motorista e mover o marcador do motorista pelo mapa, simulando a localização do motorista. (Mais sobre isso abordado na seção front-end.)
Quando o driver se move, um evento de soquete é acionado, enviando a localização atualizada para o servidor, que é então atualizada no banco de dados.
1socket.on("UPDATE_DA_LOCATION", async (data) => {
2  const { email, location } = data;
3  await collection.findOneAndUpdate({ email }, { $set: { currentLocation: location } });
4});
O código acima trata o evento de soquete "UPDATE_DA_LOCATION". Ele recebe os dados de e-mail e localização da mensagem do soquete e atualiza a localização atual do driver correspondente no MongoDB database.
Até agora, abordamos como configurar um servidor Express e conectá-lo ao MongoDB. Também vimos como configurar o Socket.IO e ouvir as atualizações. Na próxima seção, abordaremos como usar change stream e emitir um evento de soquete do servidor para o front-end.

Usando change streams para ler atualizações

Este é o ponto central da discussão neste artigo. Quando uma nova entrega é solicitada da interface do usuário, uma entrada de remessa é criada no banco de dados. A remessa ficará em estado pendente até que um motorista a aceite.
Depois que o driver aceita a remessa, é criada uma sala de soquete com o ID do driver como o nome da sala, e o usuário que criou a remessa é inscrito nessa sala.
Veja um diagrama simples para ajudá-lo a visualizar melhor o fluxo.
Diagrama de fluxo de criação de remessa
Com o usuário inscrito na sala de soquete, tudo o que precisamos fazer é ouvir as mudanças na localização do driver. É aqui que o fluxo de alterações entra em cena.
Temos um change stream em vigor, que está ouvindo a collection Delivery Associate (Driver). Sempre que houver uma atualização na collection, ela será acionada. Usaremos essa função de retorno de chamada para executar nossa lógica de negócios.
Observe que estamos passando uma opção para a função de observação do fluxo de alterações { fullDocument: 'updateLookup' }. Ela especifica que o documento atualizado completo deve ser incluído no evento de alteração, em vez de apenas o delta ou as alterações feitas no documento.
1const watcher = async (io: Server) => {\
2  const collection = await DeliveryAssociateCollection();\
3  const changeStream = collection.watch([], { fullDocument: 'updateLookup' });\
4  changeStream.on('change', (event) => {\
5    if (event.operationType === 'update') {\
6        const fullDocument = event.fullDocument;\
7        io.to(String(fullDocument._id)).emit("DA_LOCATION_CHANGED", fullDocument);\
8}});};
No código acima, estamos escutando todas as operações CRUD na collection Delivery Associado (Driver) e emitimos eventos de soquete apenas para operações de atualização. Como os nomes das salas são apenas IDs de driver, podemos obter a ID do driver no documento atualizado.
Dessa forma, podemos ouvir as alterações na localização do driver usando change streams e enviá-las ao usuário. 
Na base de código, todo o código de fluxo de alterações do aplicativo estará dentro da pasta src/watchers/. Você pode especificar os observadores onde desejar, mas, para manter o código limpo, estou seguindo essa abordagem. O código abaixo mostra como a função watcher é executada no ponto de entrada do aplicativo, ou seja, no arquivo server.ts.
1// server.ts
2import deliveryAssociateWatchers from './watchers/deliveryAssociates';
3server.listen(5000, async () => {
4  try {
5 await dbClient();
6 socketHandler(io);
7 await deliveryAssociateWatchers(io);
8  } catch (error) {
9    console.error(error);
10  }
11});
Nesta seção, vimos como os fluxos de alterações são usados para monitorar atualizações na coleção Associado à entrega (driver). Também vimos como a opçãofullDocument na função de inspetor foi usada para recuperar o documento atualizado completo, o que nos possibilitou enviar os dados de localização atualizados para o usuário inscrito por meio de soquetes. A próxima seção se concentra em explorar a base de código front-end e como os dados emitidos são usados para atualizar o mapa em tempo real.

frontend

Não entrarei em muitos detalhes no front-end, mas apenas para lhe dar uma visão geral, ele é baseado no React e usa Leaflet.js para Map.
Incluí todo o frontend como um subaplicativo no repositório do Github na pasta /frontend. O Readme contém as etapas sobre como instalar e iniciar o aplicativo.
Iniciar o frontend oferece duas opções: 
1. Faça login como usuário.2. Faça login como driver.
Use a opção "log in as driver" para simular a localização do motorista. Isto pode ser feito simplesmente arrastando o marcador pelo mapa.
tela de boas-vindas frontend

Simulador de driver

Fazer login como motorista permitirá simular a localização do motorista. O snippet de código fornecido demonstra o uso de ganchos deuseStatee useEffect para simular as atualizações de localização de um driver. O <MapContainer> e o<DraggableMarker> são componentes do folheto. Um é o mapa real que vemos na interface do usuário e o outro é, como o nome sugere, um marcador que pode ser movido usando o mouse.
1// Driver Simulator
2const [position, setPosition] = useState(initProps.position);
3const gpsUpdate = (position) => {
4  const data = {
5    email,
6    location: { type: 'Point', coordinates: [position.lng, position.lat] },
7  };
8  socket.emit("UPDATE_DA_LOCATION", data);
9};
10useEffect(() => {
11gpsUpdate(position);
12}, [position]);
13return (
14<MapContainer>
15<DraggableMarker position={position}/>
16</MapContainer>
17)
O estado daposição é inicializado com as props iniciais. Quando o marcador arrastável é movido, a posição é atualizada. Isso aciona a função gpsUpdate dentro de seu gancho useEffect, que envia um evento de soquete para atualizar a localização do driver.

Aplicativo de usuário

No lado do aplicativo do usuário, quando uma nova remessa é criada e um associado de entrega é atribuído, o evento de soquete SHIPMENT_UPDATEDé acionado. Em resposta, o aplicativo do usuário emite o eventoSUBSCRIBE_TO_DA para assinar o socket room do motorista (DA é a abreviação de Delivery Associate, associado de entrega).
1socket.on('SHIPMENT_UPDATED', (data) => {
2  if (data.deliveryAssociateId) {
3    const deliveryAssociateId = data.deliveryAssociateId;
4    socket.emit('SUBSCRIBE_TO_DA', { deliveryAssociateId });
5  }
6});
Uma vez inscrito, qualquer alteração na localização do motorista trigger o evento de soquete DA_LOCATION_CHANGED. O estadodriverPositionrepresenta a posição atual do motorista de entrega. Ele é atualizado sempre que novos dados são recebidos do evento de soquete.
1const [driverPosition, setDriverPosition] = useState(initProps.position);
2socket.on('DA_LOCATION_CHANGED', (data) => {
3  const location = data.location;
4  setDriverPosition(location);
5});
6return (
7<MapContainer>
8<Marker position={driverPosition}/>
9</MapContainer>
10)
O código demonstra como o aplicativo do usuário atualiza a posição do marcador do motorista no mapa em tempo real usando eventos de soquete. O estado driverPosition é passado para o
componente e atualizado com os dados de localização mais recentes do evento de soquete DA_LOCATION_CHANGED.

Resumo

Neste artigo, vimos como o MongoDB Change Streams e Socket.IO podem ser usados em um aplicativo Node.js Express para desenvolver um sistema de tempo real.
Avaliamos como monitorar uma coleção do MongoDB usando o método change stream watcher. Também aprendemos como as salas Socket.IO podem ser usadas para segmentar conexões de soquete para transmitir atualizações. Também vimos um pouco de código de front-end sobre como as props são manipuladas com eventos de soquete.
Se quiser saber mais sobre Change Streams, confira nosso tutorial sobre Change Streams e triggers com Node.jsou a versão em vídeo dele. Para obter um tutorial mais detalhado sobre como usar o Change Streams diretamente em seu aplicativo React, você também pode conferir este tutorial sobre dados em tempo real em um front-end JavaScript do React.

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Artigo

Análise de moeda com coleções de séries temporais # 3 — Cálculo de MACD e RSI


Sep 11, 2024 | 8 min read
Início rápido

Como conectar o MongoDB Atlas ao Vercel usando a nova integração


Aug 05, 2024 | 4 min read
Tutorial

Como fazer semantic search no MongoDB utilizando o Atlas Vector Search


Sep 18, 2024 | 8 min read
Tutorial

Combinando seu banco de dados com o Azure Blob Storage usando o Data Federation


Oct 08, 2024 | 7 min read
Sumário