An open API service indexing awesome lists of open source software.

https://github.com/openliberty/guide-mongodb-intro

A guide on how to persist data in your microservices to MongoDB, a document-oriented NoSQL database.
https://github.com/openliberty/guide-mongodb-intro

Last synced: about 1 year ago
JSON representation

A guide on how to persist data in your microservices to MongoDB, a document-oriented NoSQL database.

Awesome Lists containing this project

README

          

// Copyright (c) 2020, 2025 IBM Corporation and others.
// Licensed under Creative Commons Attribution-NoDerivatives
// 4.0 International (CC BY-ND 4.0)
// https://creativecommons.org/licenses/by-nd/4.0/
//
// Contributors:
// IBM Corporation
//
:projectid: mongodb-intro
:page-layout: guide-multipane
:page-duration: 25 minutes
:page-releasedate: 2020-12-17
:page-essential: true
:page-essential-order: 1
:page-description: Learn how to persist data in Java microservices to MongoDB, a document-oriented NoSQL database.
:guide-author: Open Liberty
:page-tags: ['microprofile', 'jakarta-ee']
:page-related-guides: ['cdi-intro', 'microprofile-config', 'rest-intro']
:page-permalink: /guides/{projectid}
:repo-description: Visit the https://openliberty.io/guides/{projectid}.html[website] for the rendered version of the guide.
:page-seo-title: Persisting data in your Java microservices to MongoDB, a document-oriented NoSQL database
:page-seo-description: A getting started tutorial with examples on how to connect, access and persist data to MongoDB, a document-oriented NoSQL database, in cloud-native Java applications using Contexts and Dependency Injection (CDI) and Eclipse MicroProfile Config with Open Liberty.
:common-includes: https://raw.githubusercontent.com/OpenLiberty/guides-common/prod
= Persisting data with MongoDB

[.hidden]
NOTE: This repository contains the guide documentation source. To view the guide in published form, view it on the https://openliberty.io/guides/{projectid}.html[Open Liberty website].

Learn how to persist data in your microservices to MongoDB, a document-oriented NoSQL database.

== What you'll learn

You will learn how to use MongoDB to build and test a simple microservice that manages the members of a crew. The microservice will respond to `POST`, `GET`, `PUT`, and `DELETE` requests that manipulate the database.

The crew members will be stored in MongoDB as documents in the following JSON format:

[source,json,role="no_copy"]
----
{
"_id": {
"$oid": "5dee6b079503234323db2ebc"
},
"Name": "Member1",
"Rank": "Captain",
"CrewID": "000001"
}
----

This microservice connects to MongoDB by using Transport Layer Security (TLS) and injects a `MongoDatabase` instance into the service with a Contexts and Dependency Injection (CDI) producer. Additionally, MicroProfile Config is used to easily configure the MongoDB driver.

For more information about CDI and MicroProfile Config, see the guides on https://openliberty.io/guides/cdi-intro.html[Injecting dependencies into microservices^] and https://openliberty.io/guides/microprofile-config-intro.html[Separating configuration from code in microservices^].

// =================================================================================================
// Additional Prerequisites
// =================================================================================================

== Additional prerequisites

You will use Docker to run an instance of MongoDB for a fast installation and setup. Install Docker by following the instructions in the official https://docs.docker.com/engine/installation[Docker documentation^], and start your Docker environment.

// =================================================================================================
// Getting Started
// =================================================================================================

[role='command']
include::{common-includes}/gitclone.adoc[]

// =================================================================================================
// Setting up MongoDB
// =================================================================================================

=== Setting up MongoDB

This guide uses Docker to run an instance of MongoDB. A multi-stage Dockerfile is provided for you. This Dockerfile uses the `mongo` image as the base image of the final stage and gathers the required configuration files. The resulting `mongo` image runs in a Docker container, and you must set up a new database for the microservice. Lastly, the truststore that's generated in the Docker image is copied from the container and placed into the Open Liberty configuration.

You can find more details and configuration options on the https://docs.mongodb.com/manual/reference/configuration-options/[MongoDB website^]. For more information about the `mongo` image, see https://hub.docker.com/_/mongo[mongo^] in Docker Hub.

**Running MongoDB in a Docker container**

Run the following commands to use the Dockerfile to build the image, run the image in a Docker container, and map port `27017` from the container to your host machine:

ifdef::cloud-hosted[]
```bash
sed -i 's=latest=7.0.15-rc1=g' assets/Dockerfile
```
endif::[]

[role='command']
```
docker build -t mongo-sample -f assets/Dockerfile .
docker run --name mongo-guide -p 27017:27017 -d mongo-sample
```

**Adding the truststore to the Open Liberty configuration**

The truststore that's created in the container needs to be added to the Open Liberty configuration so that the Liberty can trust the certificate that MongoDB presents when they connect. Run the following command to copy the `truststore.p12` file from the container to the `start` and `finish` directories:

include::{common-includes}/os-tabs.adoc[]

[.tab_content.windows_section]
--
[role='command']
```
docker cp ^
mongo-guide:/home/mongodb/certs/truststore.p12 ^
start/src/main/liberty/config/resources/security
docker cp ^
mongo-guide:/home/mongodb/certs/truststore.p12 ^
finish/src/main/liberty/config/resources/security
```
--
[.tab_content.mac_section]
--
[role='command']
```
docker cp \
mongo-guide:/home/mongodb/certs/truststore.p12 \
start/src/main/liberty/config/resources/security
docker cp \
mongo-guide:/home/mongodb/certs/truststore.p12 \
finish/src/main/liberty/config/resources/security
```
--
[.tab_content.linux_section]
--
[role='command']
```
docker cp \
mongo-guide:/home/mongodb/certs/truststore.p12 \
start/src/main/liberty/config/resources/security
docker cp \
mongo-guide:/home/mongodb/certs/truststore.p12 \
finish/src/main/liberty/config/resources/security
```
--

// =================================================================================================
// Try what you'll build
// =================================================================================================

[role='command']
include::{common-includes}/twyb-intro.adoc[]

ifndef::cloud-hosted[]
You can now check out the service by going to the http://localhost:9080/mongo/[^] URL.
endif::[]

ifdef::cloud-hosted[]
You can now check out the service by clicking the following button:

::startApplication{port="9080" display="external" name="Launch application" route="/mongo"}
endif::[]

[role='command']
include::{common-includes}/twyb-end.adoc[]

// =================================================================================================
// Providing a MongoDatabase
// =================================================================================================

== Providing a MongoDatabase

Navigate to the `start` directory to begin.

ifdef::cloud-hosted[]
```bash
cd /home/project/guide-mongodb-intro/start
```
endif::[]

[role='command']
include::{common-includes}/devmode-lmp33-start.adoc[]

With a CDI producer, you can easily provide a `MongoDatabase` to your microservice.

[role="code_command hotspot", subs="quotes"]
----
#Create the `MongoProducer` class.#
`src/main/java/io/openliberty/guides/mongo/MongoProducer.java`
----

MongoProducer.java
[source, Java, linenums, role="code_column hide_tags=copyright"]
----
include::finish/src/main/java/io/openliberty/guides/mongo/MongoProducer.java[]
----

microprofile-config.properties
[source, properties, linenums, role="code_column hide_tags=copyright"]
----
include::finish/src/main/resources/META-INF/microprofile-config.properties[]
----

pom.xml
[source,xml,linenums,role="code_column hide_tags=copyright"]
----
include::finish/pom.xml[]
----

server.xml
[source,xml,linenums,role="code_column hide_tags=copyright"]
----
include::finish/src/main/liberty/config/server.xml[]
----

The values from the [hotspot file=1]`microprofile-config.properties` file are injected into the [hotspot=mongoProducerInjections file=0]`MongoProducer` class. The `MongoProducer` class requires the following methods for the `MongoClient`:

* The [hotspot=createMongo file=0]`createMongo()` producer method returns an instance of `MongoClient`. In this method, the username, database name, and decoded password are passed into the [hotspot=createCredential file=0]`MongoCredential.createCredential()` method to get an instance of `MongoCredential`. The [hotspot=sslContext file=0]`JSSEHelper` gets the `SSLContext` from the `outboundSSLContext` in the `server.xml` configuration file. Then, a [hotspot=mongoClient file=0]`MongoClient` instance is created.

* The [hotspot=createDB file=0]`createDB()` producer method returns an instance of `MongoDatabase` that depends on the `MongoClient`. This method injects the `MongoClient` in its parameters and passes the database name into the [hotspot=getDatabase file=0]`MongoClient.getDatabase()` method to get a `MongoDatabase` instance.

* The [hotspot=close file=0]`close()` method is a clean-up function for the `MongoClient` that closes the connection to the `MongoDatabase` instance.

// =================================================================================================
// Implementing the Create, Retrieve, Update, and Delete operations
// =================================================================================================

== Implementing the Create, Retrieve, Update, and Delete operations

You are going to implement the basic create, retrieve, update, and delete (CRUD) operations in the `CrewService` class. The [hotspot=mongoImports1]`com.mongodb.client` and [hotspot=mongoImports2]`com.mongodb.client.result` packages are used to help implement these operations for the microservice. For more information about these packages, see the https://mongodb.github.io/mongo-java-driver/5.2.1/apidocs/mongodb-driver-sync/com/mongodb/client/package-summary.html[com.mongodb.client^] and https://mongodb.github.io/mongo-java-driver/5.2.1/apidocs/mongodb-driver-core/com/mongodb/client/result/package-summary.html[com.mongodb.client.result^] Javadoc. For more information about creating a RESTful service with JAX-RS, JSON-B, and Open Liberty, see the guide on https://openliberty.io/guides/rest-intro.html[Creating a RESTful web serivce^].

[role="code_command hotspot", subs="quotes"]
----
#Create the `CrewService` class.#
`src/main/java/io/openliberty/guides/application/CrewService.java`
----

CrewService.java
[source, Java, linenums, role="code_column hide_tags=copyright"]
----
include::finish/src/main/java/io/openliberty/guides/application/CrewService.java[]
----

CrewMember.java
[source, Java, linenums, role="code_column hide_tags=copyright"]
----
include::finish/src/main/java/io/openliberty/guides/application/CrewMember.java[]
----

In this class, a [hotspot=beanValidator]`Validator` is used to validate a [hotspot=crewMember file=1]`CrewMember` before the database is updated. The CDI producer is used to inject a [hotspot=dbInjection]`MongoDatabase` into the CrewService class.

**Implementing the Create operation**

The [hotspot=add]`add()` method handles the implementation of the create operation. An instance of `MongoCollection` is retrieved with the [hotspot=getCollection]`MongoDatabase.getCollection()` method. The `Document` type parameter specifies that the `Document` type is used to store data in the `MongoCollection`. Each crew member is converted into a [hotspot=crewMemberCreation]`Document`, and the [hotspot=insertOne]`MongoCollection.insertOne()` method inserts a new crew member document.

**Implementing the Retrieve operation**

The [hotspot=retrieve]`retrieve()` method handles the implementation of the retrieve operation. The `Crew` collection is retrieved with the [hotspot=getCollectionRead]`MongoDatabase.getCollection()` method. Then, the [hotspot=find]`MongoCollection.find()` method retrieves a `FindIterable` object. This object is iterable for all the crew members documents in the collection, so each crew member document is concatenated into a String array and returned.

**Implementing the Update operation**

The [hotspot=update]`update()` method handles the implementation of the update operation. After the `Crew` collection is retrieved, a document is created with the specified object `id` and is used to query the collection. Next, a new crew member [hotspot=crewMemberUpdate]`Document` is created with the updated configuration. The [hotspot=replaceOne]`MongoCollection.replaceOne()` method is called with the query and new crew member document. This method updates all of the matching queries with the new document. Because the object `id` is unique in the `Crew` collection, only one document is updated. The `MongoCollection.replaceOne()` method also returns an `UpdateResult` instance, which determines how many documents matched the query. If there are zero matches, then the object `id` doesn't exist.

**Implementing the Delete operation**

The [hotspot=remove]`remove()` method handles the implementation of the delete operation. After the `Crew` collection is retrieved, a [hotspot=queryDelete]`Document` is created with the specified object `id` and is used to query the collection. Because the object `id` is unique in the `Crew` collection, only one document is deleted. After the document is deleted, the [hotspot=deleteOne]`MongoCollection.deleteOne()` method returns a `DeleteResult` instance, which determines how many documents were deleted. If zero documents were deleted, then the object `id` doesn't exist.

// =================================================================================================
// Configuring the MongoDB driver and the Liberty
// =================================================================================================

== Configuring the MongoDB driver and the Liberty

MicroProfile Config makes configuring the MongoDB driver simple because all of the configuration can be set in one place and injected into the CDI producer.

[role="code_command hotspot file=0", subs="quotes"]
----
#Create the configuration file.#
`src/main/resources/META-INF/microprofile-config.properties`
----

microprofile-config.properties
[source, properties, linenums, role="code_column hide_tags=copyright"]
----
include::finish/src/main/resources/META-INF/microprofile-config.properties[]
----

Values such as the hostname, port, and database name for the running MongoDB instance are set in this file. The user’s username and password are also set here. For added security, the password was encoded by using the https://openliberty.io/docs/latest/reference/command/securityUtility-encode.html[securityUtility encode command^].

To create a CDI producer for MongoDB and connect over TLS, the Open Liberty needs to be correctly configured.

[role="code_command hotspot file=1", subs="quotes"]
----
#Replace the Liberty `server.xml` configuration file.#
`src/main/liberty/config/server.xml`
----

server.xml
[source,xml,linenums,role="code_column hide_tags=copyright"]
----
include::finish/src/main/liberty/config/server.xml[]
----

The features that are required to create the CDI producer for MongoDB are https://openliberty.io/docs/latest/reference/feature/cdi.html[Contexts and Dependency Injection^] (`cdi`), https://openliberty.io/docs/latest/reference/feature/ssl.html[Secure Socket Layer^] (`ssl`), https://openliberty.io/docs/latest/reference/feature/mpConfig.html[MicroProfile Config^] (`mpConfig`), and https://openliberty.io/docs/latest/reference/feature/passwordUtilities.html[Password Utilities^] (`passwordUtilities`). These features are specified in the [hotspot=featureManager file=1]`featureManager` element. The Secure Socket Layer (SSL) context is configured in the `server.xml` configuration file so that the application can connect to MongoDB with TLS. The [hotspot=keyStore file=1]`keyStore` element points to the `truststore.p12` keystore file that was created in one of the previous sections. The [hotspot=ssl file=1]`ssl` element specifies the `defaultKeyStore` as the keystore and `outboundTrustStore` as the truststore.

After you replace the `server.xml` file, the Open Liberty configuration is automatically reloaded.

// ==================================================================================
// Running the application
// ==================================================================================

[role='command']
include::{common-includes}/devmode-build.adoc[]

ifndef::cloud-hosted[]
Go to the http://localhost:9080/openapi/ui/[^] URL to see the OpenAPI user interface (UI) that provides API documentation and a client to test the API endpoints that you create after you see a message similar to the following example:
endif::[]

ifdef::cloud-hosted[]
Wait until you see a message similar to the following example:
endif::[]

[source,role="no_copy"]
----
CWWKZ0001I: Application guide-mongodb-intro started in 5.715 seconds.
----

ifdef::cloud-hosted[]
Click the following button to see the OpenAPI user interface (UI) that provides API documentation and a client to test the API endpoints that you create:

::startApplication{port="9080" display="external" name="Visit OpenAPI UI" route="/openapi/ui"}
endif::[]

**Try the Create operation**

From the OpenAPI UI, test the create operation at the `POST /api/crew` endpoint by using the following code as the request body:

[role='command']
```
{
"name": "Member1",
"rank": "Officer",
"crewID": "000001"
}
```

This request creates a new document in the `Crew` collection with a name of `Member1`, rank of `Officer`, and crew ID of `000001`.

You'll receive a response that contains the JSON object of the new crew member, as shown in the following example:
[role="no_copy"]
```
{
"Name": "Member1",
"Rank": "Officer",
"CrewID": "000001",
"_id": {
"$oid": "<>"
}
}
```

ifndef::cloud-hosted[]
The `\<>` that you receive is a unique identifier in the collection. Save this value for future commands.
endif::[]

ifdef::cloud-hosted[]
The ***\<\\>*** that you receive is a unique identifier in the collection. Save this value for future commands.
endif::[]

**Try the Retrieve operation**

From the OpenAPI UI, test the read operation at the `GET /api/crew` endpoint. This request gets all crew member documents from the collection.

You'll receive a response that contains an array of all the members in your crew. The response might include crew members that were created in the **Try what you’ll build** section of this guide:
[role="no_copy"]
```
[
{
"_id": {
"$oid": "<>"
},
"Name": "Member1",
"Rank": "Officer",
"CrewID": "000001"
}
]
```

**Try the Update operation**

ifndef::cloud-hosted[]
From the OpenAPI UI, test the update operation at the `PUT /api/crew/{id}` endpoint, where the `{id}` parameter is the `\<>` that you saved from the create operation. Use the following code as the request body:
endif::[]

ifdef::cloud-hosted[]
From the OpenAPI UI, test the update operation at the ***PUT /api/crew/{id}*** endpoint, where the ***{id}*** parameter is the ***\<\\>*** that you saved from the create operation. Use the following code as the request body:
endif::[]

[role='command']
```
{
"name": "Member1",
"rank": "Captain",
"crewID": "000001"
}
```

This request updates the rank of the crew member that you created from `Officer` to `Captain`.

You'll receive a response that contains the JSON object of the updated crew member, as shown in the following example:

[role="no_copy"]
```
{
"Name": "Member1",
"Rank": "Captain",
"CrewID": "000001",
"_id": {
"$oid": "<>"
}
}
```

**Try the Delete operation**

ifndef::cloud-hosted[]
From the OpenAPI UI, test the delete operation at the `DELETE/api/crew/{id}` endpoint, where the `{id}` parameter is the `\<>` that you saved from the create operation. This request removes the document that contains the specified crew member object `id` from the collection.
endif::[]

ifdef::cloud-hosted[]
From the OpenAPI UI, test the delete operation at the ***DELETE/api/crew/{id}*** endpoint, where the ***{id}*** parameter is the ***\<\\>*** that you saved from the create operation. This request removes the document that contains the specified crew member object ***id*** from the collection.
endif::[]

You'll receive a response that contains the object `id` of the deleted crew member, as shown in the following example:

[role="no_copy"]
```
{
"_id": {
"$oid": "<>"
}
}
```

ifndef::cloud-hosted[]
Now, you can check out the microservice that you created by going to the http://localhost:9080/mongo/[^] URL.
endif::[]

ifdef::cloud-hosted[]
Now, you can check out the microservice that you created by clicking the following button:

::startApplication{port="9080" display="external" name="Launch application" route="/mongo"}
endif::[]

// =================================================================================================
// Testing the service
// =================================================================================================

== Testing the application

Next, you'll create integration tests to ensure that the basic operations you implemented function correctly.

[role="code_command hotspot", subs="quotes"]
----
#Create the `CrewServiceIT` class.#
`src/test/java/it/io/openliberty/guides/application/CrewServiceIT.java`
----

CrewServiceIT.java
[source, Java, linenums, role="code_column hide_tags=copyright"]
----
include::finish/src/test/java/it/io/openliberty/guides/application/CrewServiceIT.java[]
----

The test methods are annotated with the [hotspot=test1 hotspot=test2 hotspot=test3 hotspot=test4 file=0]`@Test` annotation.

The following test cases are included in this class:

* [hotspot=testAddCrewMember file=0]`testAddCrewMember()` verifies that new members are correctly added to the database.

* [hotspot=testUpdateCrewMember file=0]`testUpdateCrewMember()` verifies that a crew member's information is correctly updated.

* [hotspot=testGetCrewMembers file=0]`testGetCrewMembers()` verifies that a list of crew members is returned by the microservice API.

* [hotspot=testDeleteCrewMember file=0]`testDeleteCrewMember()` verifies that the crew members are correctly removed from the database.

[role='command']
include::{common-includes}/devmode-test.adoc[]

You'll see the following output:

[source,role="no_copy"]
----
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.application.CrewServiceIT
=== Adding 2 crew members to the database. ===
=== Done. ===
=== Updating crew member with id 5df8e0a004ccc019976c7d0a. ===
=== Done. ===
=== Listing crew members from the database. ===
=== Done. There are 2 crew members. ===
=== Removing 2 crew members from the database. ===
=== Done. ===
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.411 s - in it.io.openliberty.guides.application.CrewServiceIT
Results:
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
----

== Tearing down the environment

[role='command']
include::{common-includes}/devmode-quit-ctrlc.adoc[]

Then, run the following commands to stop and remove the `mongo-guide` container and to remove the `mongo-sample` and `mongo` images.

[role='command']
```
docker stop mongo-guide
docker rm mongo-guide
docker rmi mongo-sample
```

== Great work! You're done!

You've successfully accessed and persisted data to a MongoDB database from a Java microservice using Contexts and Dependency Injection (CDI) and MicroProfile Config with Open Liberty.

== Related Links

Learn more about MicroProfile.

https://microprofile.io/[See the MicroProfile specs^]

https://openliberty.io/docs/ref/microprofile[View the MicroProfile API^]

include::{common-includes}/attribution.adoc[subs="attributes"]