Name: microprofile-meeting-persistence
Owner: International Business Machines
Description: null
Created: 2017-09-22 15:26:20.0
Updated: 2017-10-24 18:11:42.0
Pushed: 2017-10-24 21:45:37.0
Homepage: null
Size: 201
Language: Java
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
This lab takes you through how to update the MicroProfile Meeting application to add persistence.
At the time this lab was written MicroProfile had not defined a persistence mechanism. This is not because MicroProfile does not view persistence as important, but indicates the many equally valid choices that could be made for persistence in microservices. In this lab, Cloudant is used but there are other equally valid options like JPA, MongoDB, or Cassandra.
Adapted from the blog post: Writing a simple MicroProfile application: Adding persistence
Run the following commands:
git clone https://github.com/IBM/microprofile-meeting-persistence.git
https://github.com/IBM/microprofile-meeting-persistence.git
If you completed the previous labs and installed MongoDB, make sure MongoDB is running. If you are starting fresh, make sure you install MongoDB. Depending on what platform you are on the installation instructions may be different. For this exercise you should get the community version of MongoDB from the mongoDB download-center.
od -dbpath <path to database>
The database needs to be running for the application to work. If it is not running there will be a lot of noise in the server logs.
To start writing code, the Maven pom.xml needs to be updated to indicate the dependency on MongoDB:
pom.xml
.org.mongodb
.mongo-java-driver
.2.14.3
.pom.xml
.When updating the application, we need to convert between the MongoDB representation of data and the JSON-Processing one. Rather than scatter this around this can be placed in the MeetingsUtil class:
Open the MeetingsUtil
class from meetings > Java Resources > src/main/java > net.wasdev.samples.microProfile.meetings > MeetingsUtil.java
The first method that is needed takes a MongoDB DBObject
and returns a JsonObject
. At the beginning of the file, after the class definition, add the method declaration:
ic static JsonObject meetingAsJsonObject(DBObject obj) {
ethod body will go here
This introduces one new class, the DBObject
class is in the com.mongodb
package:
rt com.mongodb.DBObject;
The first step in creating a new JsonObject
is to create a JsonObjectBuilder
using the JSON
class. All of these are already used in the class so no new imports will be required. Add the following line to the start of the method:
ic static JsonObject meetingAsJsonObject(DBObject obj) {
ObjectBuilder builder = Json.createObjectBuilder();
The JSON object for a meeting uses a field called id
. MongoDB uses _id
for the same function, so id
in JSON will map to _id
in MongoDB. To extract the id
field from the DBObject
and add it to the JSON, add this line to the method:
der.add("id", (String) obj.get("_id"));
The title
field can be directly mapped from JSON to the MongoDB object. The title
is a String but, to ensure the right add method is called, cast it to a String. Add this line:
der.add("title", (String) obj.get("title"));
The duration
field is a Long. As before it needs to be cast to a Long to ensure the right add
method is called:
der.add("duration", (Long) obj.get("duration"));
If a meeting is running it?ll have a meetingURL
field. If it isn?t running it?ll return null. If there is no meetingUR
L field, we don?t want to add it to the meeting returned so we want to only add if meetingURL
is non-null. Add this:
ng meetingURL = (String) obj.get("meetingURL");
meetingURL != null)
der.add("meetingURL", meetingURL);
Finally we need to return a JsonObject
. This can be obtained by calling the build
method on the JsonObjectBuilder
. Add this:
rn builder.build();
Next, we need a method that does the opposite, mapping from a JsonObject
to a DBObject
. This method serves two purposes: It creates a new DBObject
and it merges a JsonObject
to an existing DBObject
, so that it has two parameters rather than one. After the end of the previous method add this:
ic static DBObject meetingAsMongo(JsonObject json, DBObject mongo) {
ethod body will go here
The first thing to do in this new method is check if the DBObject
passed in is null. If it is null, a new DBObject
is created. DBObject
is abstract so a BasicDBObject
is what will be instantiated. At the beginning of the method add this (there will be a compile error after this but don?t worry, it?ll be fixed soon):
mongo == null) {
o = new BasicDBObject();
Next, the id
needs to be moved from the JsonObject
to the new DBObject
. This should only be done when a new DBObject
is created because, otherwise, a disconnect between the URL
and the JsonObject
could result in an id
being incorrectly overwritten. Add this:
o.put("_id", json.getString("id"));
This introduced a new class, the BasicDBObject
which is in the com.mongodb
package but needs to be imported:
rt com.mongodb.BasicDBObject;
The title
field is a direct mapping from the JsonObject
to the DBObject
but the JsonObject
contains a JsonString
which needs to be converted to a String. The toString
method can?t be used for this because it wraps the String literal with quotes, which isn?t required here. Fortunately JsonObject
provides a convenient getString
method for this:
o.put("title", json.getString("title"));
The duration
field is also a direct mapping but it?s a JsonNumber
in the JsonObject
, which needs to be converted to a Long to go into the DBObject
:
o.put("duration", ((JsonNumber) json.get("duration")).longValue());
This introduced the JsonNumber
, which needs to be imported:
rt javax.json.JsonNumber;
We want to get the meetingURL
but, since it might not be there, you can?t use the getString
method because it?ll throw a NullPointerException
if there is no field with that name. To get around this, use the get
method, which returns a JsonString
. A null check can then be performed and only if it is non-null will it be added to the JSON. The getString
method must be used since toString wraps the string in quotes:
String jsonString = json.getJsonString("meetingURL");
jsonString != null) {
go.put("meetingURL", jsonString.getString());
This introduced the jsonString
, which needs to be imported:
rt javax.json.JsonString;
Finally return the mongo object and save the file.
rn mongo;
The MeetingManager
currently makes use of a ConcurrentMap
to store the meetings. All the code that integrates with this needs to be updated. In this section this is done one step at a time; as a result there will be compilation errors until you get to the end:
Open the MeetingManager
class from meetings > Java Resources > src/main/java > net.wasdev.samples.microProfile.meetings > MeetingManager.java.
After the class definition delete the following line from the file:
ate ConcurrentMap<String, JsonObject> meetings = new ConcurrentHashMap<>();
To interact with MongoDB, an instance of DB
needs to be injected. This can be done using the @Resource
annotation which can identify which resource from JNDI
to inject. Add the code where the ConcurrentMap
was removed (in the previous step):
ource(name="mongo/sampledb")
ate DB meetings;
This pulls in two new classes. The MongoDB DB
class and the Java EE @Resource
annotation. These need to be imported:
rt javax.annotation.Resource;
rt com.mongodb.DB;
MongoDB stores entries in a collection in the database. The first thing to do when writing or reading is to select the collection. To simplify code later on, let?s create a convenience method to get the collection:
ic DBCollection getColl() {
rn meetings.getCollection("meetings");
This introduces a new class, the DBCollection
class, which is in the com.mongodb package. This needs to be imported:
rt com.mongodb.DBCollection;
Find and edit the add
method:
Remove the method body from the add
method. This will be replaced to update the database.
ic void add(JsonObject meeting) {
ode will be added here
First get the collection.
llection coll = getColl();
The method is given a JsonObject
and we need a DBObject
for MongoDB, so we need to convert. The API can take an existing entry from the database, so first lets see if we can find something from the database using the findOne method:
ject existing = coll.findOne(meeting.getString("id"));
This introduces a new class, the DBObject
class which is in the com.mongodb
package. This needs to be imported:
rt com.mongodb.DBObject;
Next call the MeetingsUtil
convenience method to convert from JsonObject
to DBObject
:
ject obj = MeetingsUtil.meetingAsMongo(meeting, existing);
Finally save the new or changed DBObject
back into the database:
.save(obj);
Find and update the get
method:
Remove the method body from the get
method (this will be replaced to fetch from the database):
ic JsonObject get(String id) {
ode will be added here
To get a single entry the collection needs to be obtained, an entry found by id, and then converted to a JsonObject
using the utility method created earlier. Add the following line to the get
method:
rn MeetingsUtil.meetingAsJsonObject(getColl().findOne(id));
Find and update the list
method. The list
method is slightly more complicated to update. The general structure stays the same but for loop will change. To iterate over entries in a collection a DBCursor
is used and that returns a DBObject
. The DBObject
then needs to be converted to a JsonObject
. Replace the existing loop that looks like this:
(JsonObject meeting : meetings.values()) {
lts.add(meeting);
with this one
(DBObject meeting : getColl().find()) {
lts.add(MeetingsUtil.meetingAsJsonObject(meeting));
Find and update the startMeeting
method:
This change will radically simplify the code because there is no need to create and merge multiple JsonObject
s. Instead, you can simply add the meetingURL
to the existing DBObjec
t. The id
and url
will still need to be fetched from the JsonObject
. Remove the following lines from the startMeeting
method:
Object existingMeeting = meetings.get(id);
Object updatedMeeting = MeetingsUtil.createJsonFrom(existingMeeting).add("meetingURL", url).build();
ings.replace(id, existingMeeting, updatedMeeting);
After the id
and url
are fetched, replace the removed code with the following four lines to get a collection, find the meeting entry, set the meetingURL
, and then save it back to the database:
llection coll = getColl();
ject obj = coll.findOne(id);
put("meetingURL", url);
.save(obj);
Save the file.
TheMeetingManager
is now able to persist to a MongoDB database and back. However, the server still needs to be configured to enable it. This consists of two parts: First, server configuration and, second, getting the server runtime set up.
The server configuration is part of the project so first let?s configure that:
Open the server.xml
from src > main > liberty > config > server.xml.
Add mongodb-2.0
as a new feature. It should look like this:
tureManager>
ture>microProfile-1.0</feature>
ture>mongodb-2.0</feature>
atureManager>
A shared library needs to be defined to be used by the application and the runtime for defining the MongoDB resources:
rary id="mongodriver">
e name="${shared.resource.dir}/mongo-java-driver.jar"/>
brary>
Next the mongo
needs to be defined. This tells the server where the mongo
server instance is running:
go id="mongo" libraryRef="mongodriver">
ts>27017</ports>
tNames>localhost</hostNames>
ngo>
Next, define the database:
goDB databaseName="meetings" jndiName="mongo/sampledb" mongoRef="mongo"/>
Finally, configure the application so it can see the MongoDB classes:
Application location="meetings-${project.version}.war">
ssloader commonLibraryRef="mongodriver"/>
bApplication>
Save the file.
The next step is to configure the Maven build to make sure all the resources end up in the right place.
The Maven POM needs to be updated to do a few things: It needs to copy the MongoDB Java driver into the Liberty server, define an additional bootstrap property, copy the application to a different location, and ensure that the mongodb-2.0
feature is installed:
Open the pom.xml
in the root of the project.
Select the pom.xml
tab in the editor.
maven-dependency-plugin
you should see this in the file:upId>org.apache.maven.plugins</groupId>
ifactId>maven-dependency-plugin</artifactId>
sion>2.10</version>
cutions>
cution>
<id>copy-server-files</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeArtifactIds>server-snippet</includeArtifactIds>
<prependGroupId>true</prependGroupId>
<outputDirectory>${project.build.directory}/wlp/usr/servers/${wlpServerName}/configDropins/defaults</outputDirectory>
</configuration>
ecution>
ecutions>
This is copying server snippets from dependencies into the server configuration directory. We are going to add to it instructions to download the mongo-java-driver
, copy it to the usr/shared/resources
folder, and strip the version off the JAR file name. This last part means we don?t have to remember to update the server.xml
every time the dependency version is upgraded.
</execution>
closing tag:cution>
copy-mongodb-dependency</id>
se>package</phase>
ls>
<goal>copy-dependencies</goal>
als>
figuration>
<includeArtifactIds>mongo-java-driver</includeArtifactIds>
<outputDirectory>${project.build.directory}/wlp/usr/shared/resources/</outputDirectory>
<stripVersion>true</stripVersion>
nfiguration>
ecution>
project.version
into bootstrap.properties
The server.xml
references the WAR by artifact name and version. The version is referenced using a variable which needs to be provided to the server. This can easily be done using the bootstrap.properties
:
Search the pom.xml
file for the string <bootstrapProperties>
. You should see this in the file:
tstrapProperties>
ault.http.port>${testServerHttpPort}</default.http.port>
ault.https.port>${testServerHttpsPort}</default.https.port>
otstrapProperties>
Before the closing add the following line:
ject.version>${project.version}</project.version>
apps
folderThe Maven POM deploys the application into the dropins
folder of the Liberty server but this doesn?t allow a shared library to be used. So, instead, the application needs to be copied to the apps
folder:
Search in the pom.xml
for the string dropins
, you should see this:
ls>
l>copy-resources</goal>
als>
figuration>
putDirectory>${project.build.directory}/wlp/usr/servers/${wlpServerName}/dropins</outputDirectory>
Update the word dropins to be apps. It should look like this:
figuration>
putDirectory>${project.build.directory}/wlp/usr/servers/${wlpServerName}/apps</outputDirectory>
mongodb-2.0
feature from the Liberty repositoryThe mongodb-2.0
feature is not in the Liberty server installations that are stored in the Maven repository so it needs to be downloaded from the Liberty repository at build time:
Search the pom.xml
for the string package-server
, you should see this:
cution>
d>package-app</id>
hase>package</phase>
oals>
<goal>package-server</goal>
goals>
ecution>
ecutions>
Just before the </executions>
tag, add the following lines to cause the mongodb-2.0
feature to be installed:
cution>
install-feature</id>
se>package</phase>
ls>
<goal>install-feature</goal>
als>
figuration>
<features>
<acceptLicense>true</acceptLicense>
<feature>mongodb-2.0</feature>
</features>
nfiguration>
ecution>
Save the pom.xml
.
There are two ways to get the application running from within WDT:
The first is to use Maven to build and run the project:
Run the Maven install
goal to build and test the project: Right-click pom.xml in the meetings
project, click Run As? > Maven Build?, then in the Goals field type install
and click Run. The first time you run this goal, it might take a few minutes to download the Liberty dependencies.
Run a Maven build for the liberty:start-server goal
: Right-click pom.xml, click Run As? > Maven Build, then in the Goals field, type liberty:start-server
and click Run. This starts the server in the background.
Open the application, which is available at http://localhost:9080/meetings/
.
To stop the server again, run the liberty:stop-server
build goal.
The second way is to right-click the meetings
project and select Run As? > Run on Server but there are a few things to note if you do this. WDT doesn?t automatically add the MicroProfile features as you would expect so you need to manually add those. Also, any changes to the configuration in src/main/liberty/config
won?t be picked up unless you add an include.
Find out more about MicroProfile and WebSphere Liberty.
Since you have completed the previous lab, you can redeploy your app with these changes.
Login to Bluemix
ogin
Push your app to Bluemix (specifying no start)
ush --no-start <yourappname> -p wlp/usr/servers/meetingsServer
While your app is deploying, create a Compose MongoDB instance on Bluemix. Name the service instance mongo/sampledb
, leave all other default configurations. Click Create.
From your Bluemix Dashboard, find your deployed app and click on it.
On the left-hand side, click Connections. Then click Connect existing.
Find your mongo/sampledb
and click on it. It will prompt you to restage the app, click Restage.
Once the app finishes restaging, you can revisit the route/url. Be sure to add /meetings
to the end of the route to hit the home page of your app.
Part 3: MicroProfile Application - Using Java EE Concurrency