# About MongoDB Client
The "moleculer-java-mongo" is an asynchronous MongoDB client, specially designed for Java-based Moleculer Ecosystem. The API can be conveniently used with the Spring Framework (but it works without Spring).
# Download
Maven
<dependencies>
<dependency>
<groupId>com.github.berkesa</groupId>
<artifactId>moleculer-java-mongo</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
Gradle
dependencies {
implementation group: 'com.github.berkesa', name: 'moleculer-java-mongo', version: '1.0.0'
}
# Usage without Spring Framework
The key Superclass is the "MongoDAO". All DAO objects are inherited from this Class. The name of the Mongo Collection can be specified by the annotation "MongoCollection". The functions run in non-blocking mode, so all return types are Promise:
import io.datatree.*;
import services.moleculer.mongo.*;
@MongoCollection("user")
public class UserDAO extends MongoDAO {
public Promise createNewUser(String firstName, String lastName, String email) {
Tree document = new Tree();
document.put("firstName", firstName);
document.put("lastName", lastName);
document.put("email", email);
return insertOne(document);
}
public Promise getUserByEmail(String email) {
return findOne(eq("email", email));
}
public Promise deleteUserByEmail(String email) {
return deleteOne(eq("email", email));
}
public Promise countUsers() {
return count();
}
}
The use of the UserDAO is illustrated by the following example (without Spring):
public static void main(String[] args) throws Exception {
// Create MongoDB connection pool
MongoConnectionPool connection = new MongoConnectionPool();
connection.setConnectionString("mongodb://localhost");
connection.setDatabase("db");
connection.init();
// Create DAO Object and set the MongoDB connection pool
UserDAO userDAO = new UserDAO();
userDAO.setMongoConnectionPool(connection);
// Example of blocking-style processing
Tree count = userDAO.countUsers().waitFor();
System.out.println("Number of users: " + count.asInteger());
// Non-blocking, "waterfall-style" processing (this is the recommended)
Promise.resolve().then(rsp -> {
// Create new user
return userDAO.createNewUser("Tom", "Smith", "tom.smith@company.com");
}).then(rsp -> {
// Get the new record ID
String id = rsp.get("_id", "");
System.out.println("New record ID: " + id);
}).then(rsp -> {
// Find new user
return userDAO.getUserByEmail("tom.smith@company.com");
}).then(rsp -> {
// Print a property from the retrieved record
String firstName = rsp.get("firstName", "");
System.out.println("First name: " + firstName);
}).then(rsp -> {
// Delete record
return userDAO.deleteUserByEmail("tom.smith@company.com");
}).then(rsp -> {
// Print the result of the previous operation
int numberOfDeletedRecords = rsp.get("deleted", 0);
System.out.println("Success: " + (numberOfDeletedRecords > 0));
}).catchError(err -> {
// Error handler
err.printStackTrace();
}).then(rsp -> {
System.out.println("End of process.");
});
// Wait for few second before terminate the JVM
Thread.sleep(3000);
}
# Usage with Spring Framework
When using the Spring Framework, you can create the MongoDB Connection Pool as Spring Bean:
<bean id="mongoPool"
class="services.moleculer.mongo.MongoConnectionPool"
init-method="init"
destroy-method="destroy">
<property name="connectionString" value="mongodb://localhost" />
<property name="database" value="db" />
<property name="connectionTimeout" value="3000" />
</bean>
This case is different from using without Spring Framework in that the DAO classes are inherited from "SpringMongoDAO", and the stereotype of the DAO classes is "Repository":
import io.datatree.*;
import services.moleculer.mongo.*;
import org.springframework.stereotype.*;
@Repository
@MongoConnection("mongoPool")
@MongoCollection("user")
public class UserDAO extends SpringMongoDAO {
public Promise createNewUser(String firstName, String lastName, String email) {
Tree document = new Tree();
document.put("firstName", firstName);
document.put("lastName", lastName);
document.put("email", email);
return insertOne(document);
}
public Promise getUserByEmail(String email) {
return findOne(eq("email", email));
}
public Promise deleteUserByEmail(String email) {
return deleteOne(eq("email", email));
}
public Promise countUsers() {
return count();
}
}
MongoDAOs can be used like any other Spring Beans, with the "Autowired" annotation any other Spring Bean can refer to them:
@Autowired
private UserDAO userDAO;
# Usage with Moleculer Framework
Moleculer Framework allows you to build a distributed service-based application that uses MongoDB as a back-end. An example of a Moleculer-based service available through Message Broker (eg. Redis or NATS) and via HTTP REST call:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import services.moleculer.service.Action;
import services.moleculer.service.Name;
import services.moleculer.service.Service;
import services.moleculer.web.router.HttpAlias;
@Component
@Name("user")
public class UserService extends Service {
@Autowired
private UserDAO userDAO;
@Name("create")
@HttpAlias(method = "POST", path = "api/user/create")
public Action createNewUser = ctx -> {
// Get input parameter
String firstName = ctx.params.get("firstName", "");
String lastName = ctx.params.get("lastName", "");
String email = ctx.params.get("email", "");
// Verify input parameters
// ...
// Invoke UserDAO
return userDAO.createNewUser(firstName, lastName, email);
};
}
The REST part (the "HttpAlias" annotation) is optional in the code above. However, if you want to build REST services, you'll need the API Gateway dependency. From another Moleculer service (even from another host) you can use the service above with the following code:
ServiceBroker broker = ServiceBroker
.builder()
.transporter(new NatsTransporter("nats://host"))
.build();
broker.call("user.create",
"firstName", "Tom",
"lastName", "Smith",
"email", "tom.smith@company.com")
.then(rsp -> {
// User created successfully
String recordID = rsp.get("_id", "");
});
# Parallel processing
Parallel queries make sense when using clustered MongoDB. If the queries are independent, they can be started at the same time. The "Promise.all" function waits for all replies to be received:
Promise p1 = userDAO.getUserByEmail(email);
Promise p2 = roleDAO.getRolesByEmail(email);
Promise p3 = postsDAO.getPostsByEmail(email);
Promise.all(p1, p2, p3).then(rsp -> {
Tree users = rsp.get(0);
Tree roles = rsp.get(1);
Tree posts = rsp.get(2);
})
# DAO methods
# Drop collection
Drops this collection from the Database.
drop().then(res -> {
// Drop operation finished
}).then(res -> {
// ...
}).then(res -> {
// ...
}).catchError(err -> {
// Error handler
});
# Rename collection
Rename the collection.
renameCollection("db", "collection").then(res -> {
// Rename operation finished
});
# Create indexes
Creates ascending/descending/2dsphere/hash/text/etc. indexes.
createAscendingIndexes("field1", "field2").then(res -> {
// Index created successfully
});
# List indexes
Get all the indexes in this collection.
listIndexes().then(res -> {
// Operation finished
for (Tree index: res.get("rows")) {
System.out.println(index.get("name", ""));
}
int numberOfIndexes = res.get("count", 0);
});
The answer (the "res" JSON) will be similar to the following structure:
{
"count":2,
"rows":[
{
"v":1,
"key":{"_id":1},
"name":"_id_",
"ns":"db.test"
}, {
"v":1,
"key":{"a":1},
"name":"a_1",
"ns":"db.test"
}
]
}
# Insert one document
Inserts the provided document. If the document is missing an identifier, the driver should generate one.
Tree doc = new Tree();
doc.put("field1", 123);
doc.put("field2.subfield", false);
insertOne(doc).then(res -> {
// Insert operation finished
// The "res" is a JSON structure,
// with the inserted document + the "_id" field
String id = res.get("_id", "");
return id;
});
# Replace one document
Replace a document in the collection according to the specified arguments.
Tree replacement = new Tree();
replacement.put("field1", 345);
replaceOne(eq("field1", 123), replacement).then(res -> {
// Replace operation finished
int modified = res.get("modified");
return modified > 0;
});
# Update one document
Update a single document in the collection according to the specified arguments.
Tree update = new Tree();
update.put("field1", 345);
updateOne(eq("field1", 123), update).then(res -> {
// Replace operation finished
int modified = res.get("modified");
return modified > 0;
});
The answer (the "res" JSON) will be similar to the following structure:
{
"matched": 10,
"modified": 4,
"acknowledged": true
}
# Update many documents
Update all documents in the collection according to the specified arguments.
Tree update = new Tree();
update.put("field1", 345);
updateMany(eq("field1", 123), update).then(res -> {
// Replace operation finished
int modified = res.get("modified");
return modified > 0;
});
# Delete one document
Removes at most one document from the collection that matches the given filter. If no documents match, the collection is not modified.
deleteOne(eq("field1", 123)).then(res -> {
// Delete operation finished
int deleted = res.get("deleted");
return deleted > 0;
});
The answer (the "res" JSON) will be similar to the following structure:
{
"deleted": 1,
"acknowledged": true
}
# Delete all documents
Removes all documents from the collection.
deleteAll().then(res -> {
// Delete operation finished
int deleted = res.get("deleted");
return deleted > 0;
});
# Delete many documents
Removes all documents from the collection that match the given query filter. If no documents match, the collection is not modified.
deleteMany(eq("field1", 123)).then(res -> {
// Delete operation finished
int deleted = res.get("deleted");
return deleted > 0;
});
# Count documents
Counts the number of documents in the collection according to the given filters.
count(eq("field1", 123)).then(res -> {
// Count operation finished
long numberOfDocuments = res.asLong();
return res;
});
# Find one document
Finds one document by the specified query filter.
findOne(eq("field1", 123)).then(res -> {
// Find operation finished
if (res != null) {
String firstName = res.get("firstName", "");
int age = res.get("age", 0);
}
return res;
});
# Find many documents
Queries the specified number of records from the collection.
find(eq("field1", 123), null, 0, 10).then(res -> {
// Find operation finished
int maxNumberOfSelectableDocuments = res.get("count");
for (Tree doc: res.get("rows")) {
String firstName = res.get("firstName", "");
}
return res;
});
The answer (the "res" JSON) will be similar to the following structure:
{
"count":10345,
"rows":[
{
"firstName": "Tom",
"lastName": "Smith",
"email": "tom.smith@company.com"
}
]
}
The "count" field contains the max number of rows which meets the "filter" condition (can be much more than the number of records in the "rows" structure).
# Find one and delete
Atomically find a document and remove it. The answer structure is the searched document, or null.
findOneAndDelete(eq("field1", 123)).then(res -> {
// Delete operation finished
if (res != null) {
String firstName = res.get("firstName", "");
int age = res.get("age", 0);
}
return res;
});
# Find one and replace
Atomically find a document and replace it. The answer structure is the searched document, or null.
Tree replacement = new Tree();
replacement.put("field1", 345);
findOneAndReplace(eq("field1", 123), replacement).then(res -> {
// Replace operation finished
if (res != null) {
String firstName = res.get("firstName", "");
int age = res.get("age", 0);
}
return res;
});
# Find one and update
Atomically find a document and update it. The answer structure is the searched document, or null.
Tree update = new Tree();
update.put("field1", 345);
findOneAndUpdate(eq("field1", 123), update).then(res -> {
// Update operation finished
if (res != null) {
String firstName = res.get("firstName", "");
int age = res.get("age", 0);
}
return res;
});
# Map/Reduce
Aggregates documents according to the specified map-reduce function.
String mapFunction = "..."; // JavaScript
String reduceFunction = "..."; // JavaScript
mapReduce(mapFunction, reduceFunction).then(res -> {
// Operation finished
});