Lidar com dados de série temporal com o MongoDB
Tim Kelly13 min read • Published Nov 19, 2024 • Updated Nov 19, 2024
APLICATIVO COMPLETO
Avalie esse Tutorial
Embora os bancos de dados relacionais sejam o padrão comum para muitas tarefas, o MongoDB está exclusivamente preparado para lidar com aplicativos de séries temporais, desde como os dados são armazenados e acessados até como se espera que seu banco de dados de dados seja dimensionado. Neste tutorial, criaremos um aplicação de rastreamento de Navios para gerenciar uma Frota de botes que atravessam o Atlântica. Visualize Amazon o c Todos esses cenários geram quantidades enormes de dados de séries temporais à medida que a localização de cada veículo é continuamente rastreada e enviada para bancos de dados. O aplicação completo está disponível em Github Github. Então, vamos trabalhar e ver por que o MongoDB é o melhor banco de dados de dados para lidar com dados de séries temporais.
Grandes clientes, como os do setor financeiro, IoT ou comunicações, gerenciam volumes massivos de dados de séries temporais. O alto desempenho de gravação é essencial para lidar com o influxo contínuo de dados de forma eficiente, sem gargalos ou perda de dados.
As esferas típicas nas quais utilizamos dados de séries temporais são de financiamento, IoT e comunicações. Todos esses são domínios que criam um influxo massivo e contínuo de dados que precisam ser manipulados sem gargalos ou perda de dados. O MongoDB tem uma coleção de séries temporais nativa especificamente otimizada para alta taxa de transferência de gravação.
As coleções de séries temporais utilizam um formato de armazenamento em colunas subjacente e armazenam dados em ordem de tempo. Esse formato fornece maior eficiência de query, uso de disco reduzido, E/S reduzida para operações de leitura e aumento do uso de cache do WiredTiger.
Agora vamos colocá-los uns contra os outros: MongoDB versus bancos de dados relacionais! Fazer tudo isso com bancos de dados relacionais é possível, mas geralmente precisa de ajustes, como particionamento de tabelas e uso de extensões, ou um banco de banco de dados de séries temporais dedicado, para corresponder ao desempenho de escrita do MongoDB. Isso requer mais configuração e manutenção contínua.
Setores como financeiros, de logstica e de serviços em nuvem enfrentam demanda cada vez maior por escalabilidade do banco de dados de dados. À medida que os volumes de dados se expandem, esses clientes precisam escalar seus bancos de dados horizontalmente para manter o desempenho e atender às solicitações dos usuários com eficiência.
No MongoDB, o dimensionamento horizontal é simplificado por meio do compartilhamento nativo . A fragmentação permite a distribuição de dados em vários servidores ou nós, de modo que nenhum servidor se torne um gargalo. Isso oferece alta disponibilidade, maior taxa de transferência de leitura e escrita e capacidade de armazenamento praticamente ilimitada. Sempre podemos adicionar outro computador!
A fragmentação é essencial para manter o alto desempenho à medida que os dados são dimensionados. Por outro lado, um banco de banco de dados popular como o PostgreSQL depende do dimensionamento vertical por padrão e pode exigir soluções externas complexas como o Citus para dimensionamento horizontal. Essas soluções aumentam o tempo de configuração e a complexidade operacional, tornando a abordagem integrada do MongoDB mais direta e tentadora para clientes com requisitos de grande escala.
Para acompanhar este tutorial, você deve ter o seguinte:
Crie um aplicação Maven Go e acesse o POM. Aqui adicionaremos nossas dependências.
1 <dependencies> 2 <dependency> 3 <groupId>org.mongodb</groupId> 4 <artifactId>mongodb-driver-sync</artifactId> 5 <version>5.2.0</version> 6 </dependency> 7 <dependency> 8 <groupId>io.javalin</groupId> 9 <artifactId>javalin</artifactId> 10 <version>6.3.0</version> 11 </dependency> 12 <dependency> 13 <groupId>com.fasterxml.jackson.core</groupId> 14 <artifactId>jackson-databind</artifactId> 15 <version>2.17.2</version> 16 </dependency> 17 </dependencies>
Estamos usando o MongoDB Java Driver para acessar nossa interação com nosso banco de dados de dados . Em seguida, temos o Javalin para criar nossa API. Por último, estamos usando o jackson databind para, sim, vincular nossos dados.
Crie uma classe MongodbConfig.java em um pacote de configuração para se conectar ao MongoDB e garantir que o banco de dados de dados e a coleção existam. Precisamos inicializar nosso banco de dados de dados e criar nossa coleção de séries temporais.
1 package com.mongodb.shiptracker.config; 2 3 import com.mongodb.ConnectionString; 4 import com.mongodb.MongoClientSettings; 5 import com.mongodb.client.MongoClient; 6 import com.mongodb.client.MongoClients; 7 import com.mongodb.client.MongoDatabase; 8 import com.mongodb.ServerApi; 9 import com.mongodb.ServerApiVersion; 10 import com.mongodb.client.MongoIterable; 11 import com.mongodb.client.model.CreateCollectionOptions; 12 import com.mongodb.client.model.TimeSeriesGranularity; 13 import com.mongodb.client.model.TimeSeriesOptions; 14 15 public class MongodbConfig { 16 private static final String CONNECTION_STRING = "YOUR-CONNECTION-STRING"; 17 private static MongoDatabase database; 18 19 public static MongoDatabase getDatabase() { 20 if (database == null) { 21 database = initializeDatabase(); 22 } 23 createTimeSeriesCollectionIfNotExists(database, "shipLocations"); 24 25 return database; 26 } 27 28 private static MongoDatabase initializeDatabase() { 29 ServerApi serverApi = ServerApi.builder() 30 .version(ServerApiVersion.V1) 31 .build(); 32 MongoClientSettings settings = MongoClientSettings.builder() 33 .applyConnectionString(new ConnectionString(CONNECTION_STRING)) 34 .serverApi(serverApi) 35 .build(); 36 37 MongoClient mongoClient = MongoClients.create(settings); 38 MongoDatabase db = mongoClient.getDatabase("ShipTracker"); 39 40 return db; 41 } 42 43 private static void createTimeSeriesCollectionIfNotExists(MongoDatabase db, String collectionName) { 44 45 MongoIterable<String> collections = db.listCollectionNames(); 46 for (String name : collections) { 47 if (name.equals(collectionName)) { 48 System.out.println("Time series collection '" + collectionName + "' already exists."); 49 return; 50 } 51 } 52 53 TimeSeriesOptions timeSeriesOptions = new TimeSeriesOptions("timestamp") 54 .metaField("boatId") 55 .granularity(TimeSeriesGranularity.SECONDS); 56 57 CreateCollectionOptions collOptions = new CreateCollectionOptions().timeSeriesOptions(timeSeriesOptions); 58 59 db.createCollection(collectionName, collOptions); 60 System.out.println("Time series collection '" + collectionName + "' created."); 61 } 62 }
Adicione sua própria string de conexão para seu banco de dados de dados , que você pode obter na UI do Atlas.
Em seguida, adicionamos nosso
metaField
. Documentos de séries temporais podem conter metadados sobre cada documento. O MongoDB usa o metaField
para agrupar conjuntos de documentos, tanto para otimização de armazenamento interno quanto para eficiência de query. Confira nossos documentos Docs para saber mais sobre o metaField em metaField Considerations.Por último, temos nossa granularidade. Quando criamos uma coleção de séries temporais, o MongoDB cria automaticamente uma collection system.buckets e agrupa dados de séries temporais de entrada em buckets. Ao definir a granularidade, controlamos a frequência com que os dados são agrupados com base na taxa de ingestão de seus dados.
Você pode usar os parâmetros de bucketing personalizados bucketMaxSpanSeconds e bucketRoundingSeconds para especificar os limites do bucket e controlar com mais precisão como os dados de séries temporais são buckets.
Vamos usar segundos, pois essa é apenas uma simulação de nossos botes navegando sobre aquele grande mar azul, e seria mais do que entediante esperar por nossas atualizações de hora em hora.
Agora, precisamos de alguns modelos para interagir com nosso aplicação. Crie um modelo básico para as portas para representar suas localizações geográficas. Criar uma classe
Port
e Location
em um pacote de modelo.1 package com.mongodb.shiptracker.model; 2 3 import org.bson.Document; 4 5 import java.util.Arrays; 6 7 public class Port { 8 private String name; 9 private double latitude; 10 private double longitude; 11 12 public Port(String name, double latitude, double longitude) { 13 this.name = name; 14 this.latitude = latitude; 15 this.longitude = longitude; 16 } 17 18 public Document toDocument() { 19 return new Document("name", name) 20 .append("location", new Document("type", "Point") 21 .append("coordinates", Arrays.asList(longitude, latitude))); // GeoJSON format: [longitude, latitude] 22 } 23 24 // add your getters and setters 25 }
1 package com.mongodb.shiptracker.model; 2 3 public class Location { 4 private double latitude; 5 private double longitude; 6 7 public Location(double latitude, double longitude) { 8 this.latitude = latitude; 9 this.longitude = longitude; 10 } 11 12 // add your getters and setters 13 }
Estamos adicionando um método toDocument na parte inferior para que possamos armazenar nossas portas como GeoJSON dados GeoJSON . Isso nos permitirá aproveitar as queries geoespaciais do MongoDB.
Para este exemplo, rastrearemos botes ao longo do mar aberto. Para fazer isso, primeiro precisaremos de um
Boat
modelo.1 package com.mongodb.shiptracker.model; 2 3 import org.bson.Document; 4 import org.bson.types.ObjectId; 5 6 public class Boat { 7 private ObjectId _id; 8 private String boatId; 9 private Location location; 10 private Location destination; 11 private String startPort; 12 private String endPort; 13 14 public Boat () {} 15 16 public Boat(String boatId, Location location, Location destination) { 17 this._id = new ObjectId(); 18 this.boatId = boatId; 19 this.location = location; 20 this.destination = destination; 21 } 22 23 public String getStartPort() { 24 return startPort; 25 } 26 27 public void setStartPort(String startPort) { 28 this.startPort = startPort; 29 } 30 31 public String getEndPort() { 32 return endPort; 33 } 34 35 public void setEndPort(String endPort) { 36 this.endPort = endPort; 37 } 38 39 public Boat(String boatId, String startPort, String endPort) { 40 this._id = new ObjectId(); 41 this.boatId = boatId; 42 this.startPort = startPort; 43 this.endPort = endPort; 44 } 45 46 // add your getters and setters 47 48 public Document toDocument() { 49 return new Document("_id", _id) 50 .append("boatId", boatId) 51 .append("startLocation", new Document("latitude", location.getLatitude()) 52 .append("longitude", location.getLongitude())) 53 .append("destination", new Document("latitude", destination.getLatitude()) 54 .append("longitude", destination.getLongitude())); 55 } 56 }
Assim como antes, estamos adicionando um
toDocument
método, mas isso não é tudo. Precisamos dos nossos botes para atravessar esse profundo mar azul. Adicione uma mudança de método para a parte inferior do seu modelo de bote.1 // Simulate movement by moving the boat incrementally towards its destination 2 public void move() { 3 double currentLatitude = location.getLatitude(); 4 double currentLongitude = location.getLongitude(); 5 double destLatitude = destination.getLatitude(); 6 double destLongitude = destination.getLongitude(); 7 8 double nauticalMilesPerHour = 20; 9 double stepSizeInDegrees = nauticalMilesPerHour / 60.0; // Convert nautical miles to degrees 10 11 // Calculate direction double latDirection = destLatitude - currentLatitude; 12 double lonDirection = destLongitude - currentLongitude; 13 double distance = Math.sqrt(latDirection * latDirection + lonDirection * lonDirection); 14 15 if (distance > stepSizeInDegrees) { 16 // Normalize direction 17 latDirection /= distance; 18 lonDirection /= distance; 19 20 // Update current location based on step size 21 double newLatitude = currentLatitude + latDirection * stepSizeInDegrees; 22 double newLongitude = currentLongitude + lonDirection * stepSizeInDegrees; 23 24 location.setLatitude(newLatitude); 25 location.setLongitude(newLongitude); 26 } else { 27 // If the boat is close enough, set it directly to the destination 28 location.setLatitude(destLatitude); 29 location.setLongitude(destLongitude); 30 } 31 }
Agora, como solicitamos um bote? Para facilitar a interface com nossa API e o mapeamento de nossas entradas JSON, teremos uma solicitação de bote.
1 package com.mongodb.shiptracker.model; 2 3 public class BoatRequest { 4 private String boatId; 5 private String startPort; 6 private String endPort; 7 8 public BoatRequest() { 9 } 10 11 // getters and setters 12 }
Isso nos permitirá fornecer um ID de bote e um porta de origem e destino, a partir da lista de portas no banco de banco de dados.
Ops , a tarefa de interface com um banco de dados de dados . Ele residirá em nosso pacote de repositório , em vários repositórios e implementações. Vamos começar com o repositório de portas.
Crie um
PortRepository
.1 package com.mongodb.shiptracker.repository; 2 3 import com.mongodb.shiptracker.model.Port; 4 5 public interface PortRepository { 6 void addPort(Port port); 7 Port getPortByName(String name); 8 }
Aqui só precisamos adicionar uma porta e obter as coordenadas de uma porta, dado seu nome. Agora, para
PortRepositoryImpl
:1 package com.mongodb.shiptracker.repository; 2 3 import com.mongodb.client.MongoCollection; 4 import com.mongodb.client.MongoDatabase; 5 import com.mongodb.shiptracker.config.MongodbConfig; 6 import com.mongodb.shiptracker.model.Port; 7 import org.bson.Document; 8 9 import java.util.List; 10 11 public class PortRepositoryImpl implements PortRepository { 12 private final MongoDatabase database; 13 14 public PortRepositoryImpl() { 15 this.database = MongodbConfig.getDatabase(); 16 } 17 18 19 public void addPort(Port port) { 20 MongoCollection<Document> collection = database.getCollection("ports"); 21 collection.insertOne(port.toDocument()); 22 } 23 24 25 public Port getPortByName(String name) { 26 MongoCollection<Document> collection = database.getCollection("ports"); 27 Document query = new Document("name", name); 28 Document result = collection.find(query).first(); 29 30 if (result != null) { 31 List<Double> coordinates = result.get("location", Document.class).getList("coordinates", Double.class); 32 return new Port( 33 result.getString("name"), 34 coordinates.get(1), // Latitude 35 coordinates.get(0) // Longitude 36 ); 37 } 38 return null; 39 } 40 }
Um tratamento de erros muito mínima, mas suficiente para nos colocar em funcionamento.
Agora, criaremos nosso ainda mais simples
BoatRepository
:1 package com.mongodb.shiptracker.repository; 2 3 import com.mongodb.shiptracker.model.Boat; 4 5 public interface BoatRepository { 6 void addBoat(Boat boat); 7 }
E implementaremos nosso método isolado:
1 package com.mongodb.shiptracker.repository; 2 3 import com.mongodb.client.MongoCollection; 4 import com.mongodb.client.MongoDatabase; 5 import com.mongodb.shiptracker.config.MongodbConfig; 6 import com.mongodb.shiptracker.model.Boat; 7 import org.bson.Document; 8 9 10 public class BoatRepositoryImpl implements BoatRepository { 11 private final MongoDatabase database; 12 13 public BoatRepositoryImpl() { 14 this.database = MongodbConfig.getDatabase(); 15 } 16 17 18 public void addBoat(Boat boat) { 19 MongoCollection<Document> collection = database.getCollection("boats"); 20 collection.insertOne(boat.toDocument()); 21 } 22 }
tão simples. Tudo o que estamos fazendo aqui é adicionar um método para adicionar um bote ao nosso banco de dados de dados.
Agora, vamos nos entreter mais um pouco aqui. Vamos criar um
TimeSeriesRepository
e adicionar um método para aproveitar a capacidade do MongoDB de lidar com queries geoespaciais em uma coleção de séries temporais.1 package com.mongodb.shiptracker.repository; 2 3 import com.mongodb.shiptracker.model.Location; 4 import org.bson.Document; 5 6 import java.util.List; 7 8 public interface TimeSeriesRepository { 9 void logBoatLocation(String boatId, Location location); 10 List<Document> calculateTotalDistanceTraveled(); 11 }
Vamos ter um método para calcular a distância total percorrida por todos os botes que navegam atualmente em nosso aplicação.
1 package com.mongodb.shiptracker.repository; 2 3 import com.mongodb.client.AggregateIterable; 4 import com.mongodb.client.MongoCollection; 5 import com.mongodb.client.MongoDatabase; 6 import com.mongodb.client.model.*; 7 import com.mongodb.shiptracker.config.MongodbConfig; 8 import com.mongodb.shiptracker.model.Location; 9 import org.bson.Document; 10 11 import java.util.ArrayList; 12 import java.util.Arrays; 13 import java.util.Date; 14 import java.util.List; 15 16 17 public class TimeSeriesRepositoryImpl implements TimeSeriesRepository { 18 19 private final MongoDatabase database; 20 21 public TimeSeriesRepositoryImpl() { 22 this.database = MongodbConfig.getDatabase(); 23 } 24 25 26 public void logBoatLocation(String boatId, Location location) { 27 MongoCollection<Document> collection = database.getCollection("shipLocations"); 28 Document geoJsonLocation = new Document("type", "Point") 29 .append("coordinates", Arrays.asList(location.getLongitude(), location.getLatitude())); 30 31 Document logEntry = new Document("boatId", boatId) 32 .append("timestamp", new Date()) 33 .append("location", geoJsonLocation); 34 35 collection.insertOne(logEntry); 36 } 37 38 39 public List<Document> calculateTotalDistanceTraveled() { 40 MongoCollection<Document> collection = database.getCollection("shipLocations"); 41 42 // Step 1: Set window fields to shift coordinates for calculating the previous position 43 Document setWindowFieldsStage = new Document("$setWindowFields", 44 new Document("partitionBy", "$boatId") 45 .append("sortBy", new Document("timestamp", 1L)) 46 .append("output", 47 new Document("previousCoordinates", 48 new Document("$shift", 49 new Document("output", "$location.coordinates") 50 .append("by", -1L) 51 ) 52 ) 53 ) 54 ); 55 56 // Step 2: Calculate the distance between current and previous coordinates 57 Document setDistanceStage = new Document("$set", 58 new Document("distance", 59 new Document("$sqrt", 60 new Document("$add", Arrays.asList( 61 new Document("$pow", Arrays.asList( 62 new Document("$subtract", Arrays.asList( 63 new Document("$arrayElemAt", Arrays.asList("$location.coordinates", 1L)), 64 new Document("$arrayElemAt", Arrays.asList("$previousCoordinates", 1L)) 65 )), 66 2L 67 )), 68 new Document("$pow", Arrays.asList( 69 new Document("$subtract", Arrays.asList( 70 new Document("$arrayElemAt", Arrays.asList("$location.coordinates", 0L)), 71 new Document("$arrayElemAt", Arrays.asList("$previousCoordinates", 0L)) 72 )), 73 2L 74 )) 75 )) 76 ) 77 ) 78 ); 79 80 // Step 3: Group by boatId and calculate the total distance 81 Document groupTotalDistanceStage = new Document("$group", 82 new Document("_id", "$boatId") 83 .append("totalDistance", new Document("$sum", "$distance")) 84 ); 85 86 // Perform the aggregation and collect results into a list 87 AggregateIterable<Document> result = collection.aggregate(Arrays.asList(setWindowFieldsStage, setDistanceStage, groupTotalDistanceStage)); 88 List<Document> resultList = new ArrayList<>(); 89 result.forEach(resultList::add); 90 91 return resultList; 92 } 93 94 }
Embora essa agregação possa não estar perfeitamente otimizada, ela mostra alguns recursos interessantes. O MongoDB é um banco de banco de dados de séries temporais , quando implementado com uma coleção de séries temporais. Ele fornece agregação pipeline stages especificamente para analisar dados de séries temporais, como $setWindowFields, que podemos usar para aplicar um ou mais operadores e um período de tempo específico em nossos dados.
A primeira etapa descobre onde cada bote estava pouco antes do registro atual:
ele agrupa os dados por ID do bote e, em seguida, classifica os dados por tempo, para que as posições mais recentes estejam em ordem. Para cada registro, é adicionado um novo campo chamado
ele agrupa os dados por ID do bote e, em seguida, classifica os dados por tempo, para que as posições mais recentes estejam em ordem. Para cada registro, é adicionado um novo campo chamado
previousCoordinates
, que contém a posição gps do registro de data/hora anterior.É como dizer: "Para cada posição, qual foi a última posição do bote?"
Agora que cada registro conhece as posições atual e anterior, ele calcula a distância que o bote percorreu entre esses dois pontos:
Agora que cada registro conhece as posições atual e anterior, ele calcula a distância que o bote percorreu entre esses dois pontos:
A fórmula que ele usa é o teorema de Pitágoras. É mais ou menos assim:
- Encontre a diferença entre a latitude atual e anterior.
- Encontre a diferença entre a longitude atual e a anterior.
- Eleve as duas diferenças e adicione-as.
- Pegue a raiz quadrada da soma para obter a distância.
Isso adiciona um novo campo a cada registro chamado
distance
, que é a distância que o bote se moveu entre as duas posições.Por fim, calcula a distância total que cada bote percorreu: agrupa todos os registros de cada bote. Por último, soma todos os valores de distância de cada bote para obter a distância total percorrida.
Leia mais sobre o que está disponível com dados de séries temporais e veja o que mais é possível com os estágios de agregação .
Agora que temos os repositórios criados e implementados, é hora de começar a trabalhar ( lógica ).
Vamos começar com o serviço de bote. Crie um pacote de serviço e adicionaremos nosso
BoatService
lá.1 package com.mongodb.shiptracker.service; 2 3 import com.mongodb.shiptracker.model.Boat; 4 import com.mongodb.shiptracker.model.Location; 5 import com.mongodb.shiptracker.model.Port; 6 import com.mongodb.shiptracker.repository.BoatRepositoryImpl; 7 import com.mongodb.shiptracker.repository.PortRepositoryImpl; 8 9 public class BoatService { 10 private final PortRepositoryImpl portRepository; 11 private final BoatRepositoryImpl boatRepository; 12 13 public BoatService() { 14 this.portRepository = new PortRepositoryImpl(); 15 this.boatRepository = new BoatRepositoryImpl(); 16 } 17 18 public Boat createBoat(String boatId, String startPortName, String endPortName) { 19 Port startPort = portRepository.getPortByName(startPortName); 20 Port endPort = portRepository.getPortByName(endPortName); 21 22 if (startPort != null && endPort != null) { 23 Boat boat = new Boat( 24 boatId, 25 new Location(startPort.getLatitude(), startPort.getLongitude()), 26 new Location(endPort.getLatitude(), endPort.getLongitude()) 27 ); 28 boatRepository.addBoat(boat); 29 return boat; 30 } else { 31 return null; 32 } 33 } 34 }
Aqui, criaremos nossos botes. Aceitamos os nomes dos portas de início e fim e obtemos as coordenadas necessárias para inicializar a localização do bote e o destino pretendido.
Em seguida, temos nosso
PortService
.1 package com.mongodb.shiptracker.service; 2 3 import com.mongodb.shiptracker.model.Port; 4 import com.mongodb.shiptracker.repository.PortRepositoryImpl; 5 6 import java.util.Arrays; 7 import java.util.List; 8 9 public class PortService { 10 private final PortRepositoryImpl portRepository; 11 12 public PortService() { 13 this.portRepository = new PortRepositoryImpl(); 14 } 15 16 public void insertInitialPorts() { 17 List<Port> ports = Arrays.asList( 18 new Port("New York", 40.7128, -74.0060), 19 new Port("Rotterdam", 51.9244, 4.4777), 20 new Port("Savannah", 32.0835, -81.0998), 21 new Port("Antwerp", 51.2194, 4.4025), 22 new Port("Miami", 25.7617, -80.1918), 23 new Port("Lisbon", 38.7223, -9.1393), 24 new Port("Halifax", 44.6488, -63.5752), 25 new Port("Le Havre", 49.4944, 0.1079), 26 new Port("Charleston", 32.7765, -79.9311), 27 new Port("Hamburg", 53.5511, 9.9937) 28 ); 29 ports.forEach(portRepository::addPort); 30 } 31 }
Isso inicializará nosso aplicação com uma lista de portas ao redor do mundo quando for chamado. Agora, em uma inspeção cuidadosa, você pode notar que esta não é a localização exata dessas portas, mas, em minha resposta, essas informações são mais difíceis de encontrar do que você poderia esperar. Ele servirá para nossa demonstração (ignore o início e o fim de cada viagem em que o bote atravessa milagrosamente a terra).
Agora, temos um muito
DistanceService
simples.1 package com.mongodb.shiptracker.service; 2 3 import com.mongodb.shiptracker.repository.TimeSeriesRepositoryImpl; 4 import org.bson.Document; 5 6 import java.util.List; 7 8 public class DistanceService { 9 private final TimeSeriesRepositoryImpl timeSeriesRepository; 10 11 public DistanceService() { 12 this.timeSeriesRepository = new TimeSeriesRepositoryImpl(); 13 } 14 15 public List<Document> calculateTotalDistanceTraveled() { 16 return timeSeriesRepository.calculateTotalDistanceTraveled(); 17 } 18 }
Para fazer as coisas funcionarem sem georreferenciar um grupo de Navios de verdade navegando em alto-mar (meu gerente negado o pedido de orçamento), vamos criar uma rápida classe de simulação.
1 package com.mongodb.shiptracker.service; 2 3 import com.mongodb.shiptracker.model.Boat; 4 import com.mongodb.shiptracker.repository.TimeSeriesRepositoryImpl; 5 6 import java.util.ArrayList; 7 import java.util.List; 8 9 public class Simulator { 10 private final List<Boat> boats; 11 private final TimeSeriesRepositoryImpl timeSeriesRepositoryImpl; 12 13 public Simulator() { 14 this.boats = new ArrayList<>(); 15 this.timeSeriesRepositoryImpl = new TimeSeriesRepositoryImpl(); // Initialize the time series repository 16 } 17 18 public void addBoat(Boat boat) { 19 boats.add(boat); 20 } 21 22 // Run the simulation until each boat reaches its destination 23 public void runSimulation() { 24 boolean hasActiveBoats = true; 25 26 while (hasActiveBoats) { 27 System.out.println("Simulation step"); 28 hasActiveBoats = false; 29 30 // Move all boats in one step and log their locations 31 for (Boat boat : new ArrayList<>(boats)) { 32 if (!(boat.getLocation().getLatitude() == boat.getDestination().getLatitude() && 33 boat.getLocation().getLongitude() == boat.getDestination().getLongitude())) { 34 boat.move(); 35 timeSeriesRepositoryImpl.logBoatLocation(boat.getBoatId(), boat.getLocation()); // Log the new location 36 37 System.out.println("Boat " + boat.getBoatId() + " moved to new position: (" + 38 boat.getLocation().getLatitude() + ", " + boat.getLocation().getLongitude() + ")"); 39 40 hasActiveBoats = true; 41 } else { 42 System.out.println("Boat " + boat.getBoatId() + " has reached its destination."); 43 } 44 } 45 46 // Remove boats that have reached their destinations after all have been processed 47 boats.removeIf(boat -> boat.getLocation().getLatitude() == boat.getDestination().getLatitude() && 48 boat.getLocation().getLongitude() == boat.getDestination().getLongitude()); 49 50 try { 51 Thread.sleep(1000); // Pause for 1 second between steps to simulate real-time updates 52 } catch (InterruptedException e) { 53 e.printStackTrace(); 54 } 55 } 56 System.out.println("All boats have reached their destinations. Simulation complete."); 57 } 58 }
Isso nos permitirá simular os botes em nossos bancos de dados se mudando para seu destino. Nada de especial — estamos esperando que os botes se movam em linha reta e a uma velocidade constante, atualizando a localização a cada segundo.
Para interagir com nosso aplicativo, usaremos o Javalin para criar uma API simples. Primeiro, precisamos de um pacote de controlador, onde podemos adicionar
ShipTrackerController
nosso.1 package com.mongodb.shiptracker.controller; 2 3 import com.mongodb.shiptracker.model.Boat; 4 import com.mongodb.shiptracker.model.BoatRequest; 5 import com.mongodb.shiptracker.service.BoatService; 6 import com.mongodb.shiptracker.service.DistanceService; 7 import com.mongodb.shiptracker.service.PortService; 8 import com.mongodb.shiptracker.service.Simulator; 9 import io.javalin.Javalin; 10 import io.javalin.http.Context; 11 12 public class ShipTrackerController { 13 private final PortService portService; 14 private final BoatService boatService; 15 private final Simulator simulator; 16 private final DistanceService distanceService; 17 18 public ShipTrackerController() { 19 this.portService = new PortService(); 20 this.boatService = new BoatService(); 21 this.simulator = new Simulator(); 22 this.distanceService = new DistanceService(); 23 } 24 25 public void registerRoutes(Javalin app) { 26 app.post("/ports/init", this::insertInitialPorts); // Initializes ports 27 app.post("/boats", this::createBoat); // Creates a boat 28 app.post("/simulate", this::runSimulation); // Runs the simulation 29 app.get("/boats/totalDistance", this::getTotalDistances); // Gets total distance traveled for all boats 30 31 app.exception(Exception.class, (e, ctx) -> { 32 e.printStackTrace(); // Logs the full stack trace for debugging 33 ctx.status(500).result("Internal Server Error: " + e.getMessage()); 34 }); 35 } 36 37 private void insertInitialPorts(Context ctx) { 38 portService.insertInitialPorts(); 39 ctx.status(201).result("Ports initialized."); 40 } 41 42 private void createBoat(Context ctx) { 43 System.out.println("Received JSON: " + ctx.body()); // Log raw JSON for verification 44 45 BoatRequest boatRequest = ctx.bodyAsClass(BoatRequest.class); 46 47 System.out.println(boatRequest.getStartPort()); 48 49 if (boatRequest.getBoatId() == null) { 50 ctx.status(400).result("Missing 'boatId' parameter."); 51 return; 52 } 53 if (boatRequest.getStartPort() == null) { 54 ctx.status(400).result("Missing 'startPort' parameter."); 55 return; 56 } 57 if (boatRequest.getEndPort() == null) { 58 ctx.status(400).result("Missing 'endPort' parameter."); 59 return; 60 } 61 62 Boat boat = boatService.createBoat(boatRequest.getBoatId(), boatRequest.getStartPort(), boatRequest.getEndPort()); 63 if (boat != null) { 64 simulator.addBoat(boat); 65 ctx.status(201).json(boat); 66 } else { 67 ctx.status(404).result("One or both ports not found."); 68 } 69 } 70 71 private void runSimulation(Context ctx) { 72 simulator.runSimulation(); 73 ctx.status(200).result("Simulation complete."); 74 } 75 76 private void getTotalDistances(Context ctx) { 77 ctx.json(distanceService.calculateTotalDistanceTraveled()); 78 } 79 }
Isso nos permitirá acessar toda essa lógica em que estamos trabalhando até agora. Podemos inicializar nossas portas, adicionar nosso(s) bote(s), simular o movimento de nossos botes e obter a distância total percorrida por todos os botes.
Agora, precisamos de um
ShipTrackerApp
para o nosso projeto.1 package com.mongodb.shiptracker; 2 3 import com.mongodb.shiptracker.controller.ShipTrackerController; 4 import io.javalin.Javalin; 5 6 public class ShipTrackerApp { 7 public static void main(String[] args) { 8 Javalin app = Javalin.create().start(7070); 9 10 ShipTrackerController controller = new ShipTrackerController(); 11 controller.registerRoutes(app); 12 } 13 }
Aqui criamos nosso aplicativo Javalin e o executamos na porta 7070.
Onde a verdadeira Diversão começa. Vamos compilar e executar nosso projeto com o seguinte:
1 mvn clean install 2 mvn exec:java -Dexec.mainClass="com.mongodb.shiptracker.ShipTrackerApp"
Agora, podemos inicializar nossas portas.
1 curl -X POST http://localhost:7070/ports/init
Com as portas adicionadas, vamos garantir que o endpoint do nosso bote esteja funcionando.
1 curl -X POST http://localhost:7070/boats -H "Content-Type: application/json" -d '{"boatId": "BOAT001","startPort": "Miami","endPort": "Lisbon"}'
O que é melhor do que um bote? Dois botes!
1 curl -X POST http://localhost:7070/boats -H "Content-Type: application/json" -d '{"boatId": "BOAT002","startPort": "New York","endPort": "Lisbon"}'
Agora que temos algo para rastrear, vamos executar nossa simulação.
1 curl -X POST http://localhost:7070/simulate
Em nosso terminal, devemos ver os botes se movendo.
1 Simulation step 2 Boat BOAT001 moved to new position: (25.821515943344753, -79.86387750300888) 3 Boat BOAT002 moved to new position: (40.70257614347035, -73.67282349442259)
E se quisermos executar nossa agregação:
1 curl -X GET http://localhost:7070/boats/totalDistance
Devemos obter algo assim:
1 [{"_id":"BOAT001","totalDistance":230.4497823966521},{"_id":"BOAT002","totalDistance":21.333333333333417}]
Eba!
Neste tutorial, construímos uma simulação de rastreador de envio para demonstrar como criar e usar coleções de séries temporais no MongoDB. Criamos a coleção de séries temporais com o Java driver Java e realizamos agregações que usaram os recursos de dados de séries temporais e o GeoJSON GeoJSON.
Se você encontrou este tutorial útil, não deixe de conferir mais de nossos artigos sobre como usar o MongoDB com Java Java no Centro do Desenvolvedor. Confira como criar uma playlist criada por AI IA , usando o deep learning4j.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.
Relacionado
Artigo
7 coisas que aprendi ao modelar dados para as estatísticas do YouTube
Oct 01, 2024 | 13 min read
Artigo
Criando um aplicativo Flask e MongoDB com aplicativos de contêiner do Azure
Apr 02, 2024 | 8 min read