Chapter 5. Design and Development
5.1. Overview
The source code for the Red Hat Mobile Application Platform E-Commerce example project is made available in a public github repository. This chapter briefly covers each component and its functionality. Note that in the example client applications, the JavaScript SDK is used to demonstrate both Cordova and Web App development. However, Red Hat Mobile Application Platform offers five different SDKs for eight types of mobile application development, reaching well beyond the examples described herein:
- JavaScript SDK (Cordova light, Cordova, Form apps and Web apps)
- iOS SDK (Native iOS apps)
- Android SDK (Native Android apps)
- C#/.NET Native SDK (Xamarin and Native Windows apps)
- Appcelerator SDK (Appcelerator Hybrid apps)
5.2. Health Monitor MBaaS Service
Given that a significant portion of e-commerce functionality relies on the availability of both product information and transaction processing, an MBaaS service, created from the Status Monitor Service template, is made available to clients via the cloud application. The E-Commerce microservices suite features 5 services relevant to the availability of the system from a user’s point of view. Each of those services is configured with a health check endpoint which is pinged by the Monitoring service every 5 minutes via the user-configured Check tasks performed during initial configuration.
5.2.1. Template Modifications
Status Monitor Service, like most other provided templates, is designed to utilize an internal MongoDB instance for persistence by default. The service application is built on Node.js, much like the cloud and portal applications, and manages dependencies via npm. The database connectivity dependency used within the template is mongoose. Originally built on earlier versions of the Mobile Application Platform, the template requires a few changes to allow proper integration with the newer versions of MongoDB included in more recent OpenShift and Mobile Application Platform releases.
The first change necessitated for compatibility is updating the mongoose dependency. The template initially requests version 3.8.15, which has been changed in the package.json file to 4.11.4.
"dependencies": {
...
"mongoose": "4.11.4",
...
}Second, a slight modification has been made to the application instantiation portion of the application to prevent a race condition from arising. As the template stands at the time of writing, initial database connectivity is attempted before configuration of the application has completed, resulting in errors regarding not yet instantiated or incorrect db connectionUrl info. By wrapping the application initialization code in a SDK call which checks and correctly sets the variables needed, the race condition is eliminated.
var mbaasApi = require('fh-mbaas-api');
var express = require('express');
var mbaasExpress = mbaasApi.mbaasExpress();
var cors = require('cors');
// handle race condition with OCP 3.X; call for connectionString
// and set to enviro var prior to init of monitor
mbaasApi.db({
"act": "connectionString"
}, function (err, connectionString) {
if (err) throw err;
console.log("connectionString fetched: " + connectionString);
// rest of app init code here
...
});As mentioned prior, most templates will default to using an internal MongoDB setup for persistence. However, it is possible to configure the template to use an external MongoDB instance or cluster by providing a series of environment variables. A quick look at the SDK code responsible for resolving persistence connectivity information yields a list of variables that could be provided to do just that:
if (process.env.FH_MONGODB_CONN_URL) {
return cb(undefined, process.env.FH_MONGODB_CONN_URL);
} else if (process.env.OPENSHIFT_MONGODB_DB_HOST) {
return mongoConnectionStringOS2(cb);
} else if ("openshift3" === process.env.FH_MBAAS_TYPE) {
return mongoConnectionStringOS3(cb);
} else if (process.env.FH_USE_LOCAL_DB) {
...Note that the FH_MONGODB_CONN_URL can be declared outright to provide the full connectivity string:
mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]
Backwards compatibility for OpenShift 2.X systems also yields another option where the OPENSHIFT_MONGODB_DB_HOST environment variable can be set, as well as a few more variables: OPENSHIFT_MONGODB_DB_PORT, OPENSHIFT_MONGODB_DB_USERNAME, and OPENSHIFT_MONGODB_DB_PASSWORD. OPENSHIFT_APP_NAME can also be used to define which database the application should authenticate against and utilize.
function mongoConnectionStringOS2(cb) {
debug('Running in OpenShift 2, constructing db connection string from additional env vars');
var connectionString,
host = process.env.OPENSHIFT_MONGODB_DB_HOST,
user = process.env.OPENSHIFT_MONGODB_DB_USERNAME,
pass = process.env.OPENSHIFT_MONGODB_DB_PASSWORD,
port = process.env.OPENSHIFT_MONGODB_DB_PORT,
dbname = process.env.OPENSHIFT_APP_NAME;
connectionString = mongodbUri.format({
username: user,
password: pass,
hosts: [{
host: host,
port: port
}],
database: dbname
});
process.env.FH_MONGODB_CONN_URL = connectionString;
return cb(undefined, process.env.FH_MONGODB_CONN_URL);
}If none of the mentioned variables have been added, an OpenShift 3.X system, by default, will have an FH_MBAAS_TYPE value set, thus leading the code to resolve and return internal connectivity information.
5.2.2. Cloud Application Integration
The Monitoring service features an aggregated monitoring endpoint which the cloud application utilizes to collectively evaluate the health of the microservices suite. The cloud application is configured to call this endpoint at each client request, record detailed results of the response, and mask that response down for the client to a simple ok or fail.
request.get(healthUrl, //dynamic url of aggregated health check via enviro prop
function(error, response, body) {
if (error) {
return callback(error, null);
} else if (response.statusCode !== 200) {
return callback("ERROR: bad status code " + response.statusCode, null);
} else {
var health = JSON.parse(body);
if (health.result === 'ok') {
return callback(null, '{"result": "ok"}');
} else {
console.error('health check detected a problem, status ' + health.result);
health.details.forEach(function(serviceEntry) {
console.error(serviceEntry.name + ' reported as ' + serviceEntry.result);
});
return callback(null, '{"result": "fail"}');
}
}
}
);5.2.3. Client Application Integration
Client applications use the cloud API at a regular interval to call the endpoint exposed in the cloud application to retrieve a summation of microservice suite health results and notify the user when their experience will be hindered due to a reported fail status.
$interval(function() {
AuthService.checkAvailability().then(function(data) {
$rootScope.availability = (data.status === 'ok');
console.log("health check..." + data.status + ' response to bool ' + $rootScope.availability);
});
}, 5000);Figure 5.1. Client Availability Warning

5.3. Cloud Application
In order for client applications to leverage the power of the Mobile Application Platform, a cloud application is set up to mediate requests to and from the E-Commerce microservices gateway and make available platform features such as data persistence, synchronization, caching, and push notifications.
5.3.1. Template Scaffolding
The Mobile Application Platform offers a scaffolding template for kickstarted development of Node.js cloud applications with SDK and other requirements pre-configured. The cloud application imported in the previous chapter was initially built atop the provided template.
5.3.2. SDK Configuration
As with most provided templates, a few assumptions are made about the source code which must be addressed for proper platform integration. In the case of cloud applications, an application.js file is assumed which specifies a few basic integrations, such as routing for MBaaS and cloud calls, as well as a health check endpoint & host/port information. A user-defined /cloud endpoint has been added for routing of custom cloud requests.
5.3.3. cloud.js
The majority of routing logic lies within cloud.js, as defined by the express router variable, cloud.
var cloud = new express.Router();
cloud.use(cors());
cloud.get('/products', function(req, res) {...};
cloud.post('/customers/authenticate', jsonParser, function(req, res) {...};
...All requests made from client applications are received here, logged into activity, then forwarded to appropriate services for handling. Once a response is returned from the service, it’s then packaged accordingly and returned to the calling client.
cloud.get('/products/:sku', function(req, res) {
activity.record({
"action": "Product details fetch for sku " + req.params.sku
}, function(err) {
if (err) {
res.statusCode = 500;
return res.end(util.inspect(err));
}
products.get(req.params.sku, function(err, data) {
if(err) {
res.statusCode = 500;
return res.end(util.inspect(err));
}
res.send(data);
});
});
});5.3.4. Cloud Activity Logging
In most request routings, a call to activity.record is made at the start of each process point for received client requests. The activity service works with the Mobile Application Platform data caching API to record each request made so that data usage and analysis can be performed.
activity.record({
"action": "action to record"
}, function(err) {...};The express router also allows for fetching and resetting of the activity list via the /activity context.
cloud.post('/activity/list', function(req, res) {
activity.list(req.body, function(err, data) {
if (err) {
res.statusCode = 500;
return res.end(util.inspect(err));
}
res.json(data);
});
});
cloud.post('/activity/reset', function(req, res) {
activity.reset(function(err, data) {
if (err) {
res.statusCode = 500;
return res.end(util.inspect(err));
}
res.json(data);
});
});5.3.5. Microservice MBaaS Service Call Proxying
A majority of endpoints defined within the express router configuration forward client requests to the API Gateway entry point of the e-commerce microservices suite. A few additional routes handle internal operations such as administration of activity logging. The express router is capable of handling the GET, POST, PUT, PATCH, and DELETE commands used within the microservice suite, as well as a few others not in use. It also handles the passing of URL parameters to the processing function. Requests carrying a JSON payload utilize a JSON parser, bodyParser, to properly translate the contents of the request for service consumption.
Example usage of inline parameters and JSON parsing:
cloud.post('/customers/:userId/orders', jsonParser, function(req, res) {
activity.record({
"action": "Save new user order"
}, function(err) {
if (err) {
res.statusCode = 500;
return res.end(util.inspect(err));
}
sales.saveOrder(req.params.userId, req.body, function(err, data) {
if(err) {
res.statusCode = 500;
return res.end(util.inspect(err));
}
res.send(data);
});
});
});5.4. Client Application Requirements
Both mobile and portal client applications were developed with a unified set of functional feature requirements in mind:
- Highlight featured product recommendations
- List of all available products, including product details
- Add/Remove/Checkout Cart functionality
- New user registration
- User authentication
- Session synchronization
- Automatic session timeout
- Persistent, synchronized list of a user’s 'favorites' across clients
- Payment processing
- Order details and processing status
- Notification of back-end service availability
5.5. Client Platform Integrations
A number of features available in the Mobile Application Platform are used via cloud/client cooperation to satisfy some of the listed requirements. For a full list of available Client API functionality and features, please refer to the Red Hat Mobile Application Platform 4.4 Client API documentation.
5.5.1. Data Synchronization
$fh.sync.init(dataset_id, options, callback)
The cloud sync framework requires handler functions defined at the client application level responsible for providing access to the back end data & management of data collisions. Each unique dataset being synchronized is identified by a dataset_id specified as the first parameter when calling any function of the sync framework. The platform also provides accessibility of sync events at the cloud level via request interceptors. Implementation-wise, both client applications use data sync to maintain and expose a persistent list of "favorite" products for each authenticated user.
Client application sync initialization and usage, SyncService.js:
init: function () {
var deferred = $q.defer();
console.log('initializing sync service');
$fh.sync.init({
"do_console_log": true,
"storage_strategy": "dom"
});
...
$fh.sync.manage(datasetId);
$fh.sync.notify(function (notification) {
if ('sync_complete' === notification.code) {
$fh.sync.doList(datasetId, success, fail);
}
else if ('local_update_applied' === notification.code) {
$fh.sync.doList(datasetId, success, fail);
}
else if ('remote_update_failed' === notification.code) {
var errorMsg = notification.message ? notification.message.msg ? notification.message.msg : undefined : undefined;
fail(errorMsg);
}
});
return deferred.promise;
},
getList: function() {
return promiseWrap(function (success, fail) {
$fh.sync.doList(datasetId, function (r) {
success(unwrapList(r));
}, fail);
});
},
update: function (item) {
return promiseWrap(function (success, fail) {
$fh.sync.doUpdate(datasetId, item.id, item.data, success, fail);
});
},
...Client application SyncService usage, HomeController.js:
function addToFavorites(product, $event) {
$event.stopPropagation();
SyncService.save(product);
product.addedToFavorites = true;
$timeout(function () {
product.addedToFavorites = false;
}, 700);
}
instance.addToFavorites = addToFavorites;Cloud application Sync request interceptor, sync.js:
var globalRequestInterceptor = function(dataset_id, params, cb) {
console.log('sync intercept for dataset ' + dataset_id + ' with params ' + params);
return cb(null);
};
fh.sync.globalInterceptRequest(globalRequestInterceptor);5.5.2. Data Caching
In the example client applications, data caching is used for recording the previously mentioned cloud activity list and tracking of user sessions. Caching functionality is mostly wrapped within calls made to the cloud application. For example, when a user is authenticated in the cloud via a call from a client application, a token is created and cached, which allows tracking/usage of the session from any client application, and automatic expiration after a configurable amount of time.
Cloud token management, sales.js:
exports.setCacheKey = function (cacheKey) {
key = cacheKey;
};
exports.checkToken = function (userId, callback) {
fh.cache({
act: "load",
key: key
}, function (err, res) {
if (err) {
console.error("error checking token ", err);
return callback(err, null);
}
var tokenList = JSON.parse(res);
var tokenFound = (tokenList || []).indexOf(userId) > -1;
console.log("token " + userId + " - " + tokenFound);
return callback(null, {
'tokenFound': tokenFound
});
});
};
exports.setToken = function (userId, callback) {
fh.cache({
act: "load",
key: key
}, function (err, res) {
if (err) {
console.log("error1 " + err);
return callback(err, null);
}
var tokenList = JSON.parse(res);
...
});
};
exports.removeToken = function (userId, callback) {
fh.cache({
act: "load",
key: key
}, function (err, res) {
if (err) {
return callback(err, null);
}
var tokenList = JSON.parse(res);
...
});
};While the example cloud application builds upon the more rudimentary $fh.cache() functionality for demonstrating tracking of user sessions, the Cloud API provides a more direct solution - $fh.session(). This tool natively offers most of the functionality that was manually built around $fh.cache() for token tracking demonstration. A collective session id, data object, and expiration time are cached as a part of the session object, which is available via set, get, and remove endpoints.
5.5.3. Form Builder
The Forms Builder allows mobile forms to be quickly and easily created using drag and drop components. While it’s possible to build entire applications around this functionality, integration of a single example form for collection of payment information could better handle form-specific validation and third-party payment system integration. For the purposes of this project, a payment information form was generated to showcase the feature, but not integrated into the client applications:
Similarly to MBaaS Services and APIs, Drag and Drop Apps are a sharable component, and thus housed in their own dashboard within the Mobile Application Platform.
Figure 5.2. Drag and Drop Apps Dashboard

Seen here, drag and drop components allow for easy definition of form content and configuration of each field:
Figure 5.3. Constructing the Form

Figure 5.4. Field Configuration

After construction and configuration, form themes can be created to allow for seamless visual integration into existing apps or complete customization of application appearance:
Figure 5.5. Form Theming

Once developed, forms can further be enhanced via available platform tooling to include field-specific and page-specific rule sets, data source coupling, broadcast of change notifications, submission routing, and more. For more information on using Form Builder, refer to the Red Hat Mobile Application Platform 4.4 Drag and Drop Apps Guide.
5.5.4. Cloud Storage
$fh.db(options, callback(err, res))
The $fh.db call provides access to hosted data storage, which supports CRUDL (create, read, update, delete, list) and index operations. It also supports deleteall, which deletes all the records in the specified entity.
5.5.5. Build Farms
When using the Build context within the Cordova/Ionic client application, the resulting native mobile artifacts are built via the Mobile Application Platform’s Build Farm. This tool enables automation of the build process, record keeping of previous builds, and creation of deployable artifacts for various mobile platforms without requiring the associated infrastructure and tools (e.g. building iOS binaries from a Linux OS).
5.6. Mobile Client Application
A mobile application has been implemented to showcase usage of the provided SDK to communicate with the cloud. The cloud API aids to abstract sharable functions and data sets to the cloud layer so that any number of other client applications or instances can share and collaborate with one another.
5.6.1. Cordova
In order to accommodate a variety of target platforms while preserving offline capabilities and access to device APIs, the example mobile application uses the Apache Cordova starter template provided within the Mobile Application Platform. Cordova, as one of several cross-platform frameworks supported, features HTML/CSS/JS mobile development via Node.js application. The framework is open source and widely available via npm. For more information on Cordova, visit the official documentation.
5.6.2. Ionic
The Ionic Framework, built atop Cordova, further simplifies the process of developing cross-platform applications by simplifying access to dozens of native device features via plugin support and themed interface components for rapid UI and feature development.
5.6.3. Template Scaffolding
The Mobile Application Platform offers a scaffolding template to kickstart development of Cordova and/or Ionic applications with SDK and other requirements pre-configured. Such a template was used initially for starting the example ecom-shop source code used for import.
5.6.4. SDK Configuration
As previously mentioned in the context of importing into the App Studio, a few assumptions are made about the configuration of the client. Contents of the fhconfig.json file are copied directly from the parameters given in the Connection component of the project, using the following structure:
{
"appid": "<app_id>",
"appkey": "<app_key>",
"apptitle": "<app_title>",
"connectiontag": "<connection-tag>",
"host": "<app_host>",
"projectid": "<project_id>"
}5.6.5. Cloud Communication
Communications with the cloud app from within the client are performed via SDK. Such calls are wrapped inside services that utilize $fh.cloud(…) to form and issue the request:
var register = function(user) {
var defer = $q.defer();
user.id = null;
var params = {
path: '/cloud/customers',
data: user,
method: "POST",
contentType: "application/json",
timeout: 15000
};
$fh.cloud(params, defer.resolve, defer.reject);
return defer.promise;
};5.6.6. Running Locally
Users can run an HTML5 presentation of the mobile client locally by issuing the npm install command within the home directory of the project, followed by grunt serve:
The Grunt task runner tool is assumed to be installed locally prior to execution of the following steps. Further information regarding installation can be found at the GruntJS home page.
$ grunt serve Running "serve" task Running "browserify:www/main.js" (browserify) task >> Bundle www/main.js created. Running "clean:server" (clean) task Running "connect:livereload" (connect) task Started connect web server on http://localhost:9002 Running "watch" task Waiting...
Once running, a browser tab or window will launch for the application. The user can also visit lab.html to see both iOS and Android renderings of the application:
Figure 5.6. Platform Preview

5.7. Web Portal Client Application
A complementary HTML5 web portal client application has been built atop Node.js using Angular and Bootstrap to demonstrate that cloud functionality of the Mobile Application Platform can also be made available via a singular cross-platform solution via native browser. The portal also demonstrates the capabilities of shared data usage and synchronization between both native mobile applications and web-based client applications.
Several similarities exist between the mobile and portal applications as they serve a similar end goal, demonstrating two different approaches to utilizing a Node.js and Angular-based application to serve as a cross-platform client solution. That being said, the focus of the portal application is primarily demonstration of desktop browser functionality and interactivity with the Mobile Application Platform. Further refined responsive design of the provided code could ultimately bridge the portal to support both mobile and desktop browser resolutions, however, focus within the example application was placed on demonstrating usage of larger displays to simplify navigation and functionality on the desktop.
While ultimately similar to the mobile app in function, some differences in form are readily apparent; product details and order details no longer require their own context and the featured list on the landing page now showcases several products at once with a greater level of detail.
Figure 5.7. Web Portal Application

5.7.1. SDK Configuration
SDK configuration of the client portal application is completed as follows:
5.7.1.1. application.js
Like the cloud app, the portal app is written on Node.js. Therefore, a similar initial configuration is required. Unlike the cloud app, Angular will be handling the custom routing responsibilities of the portal, so the application.js file is left intact to define only those configurations assumed by the Mobile Application Platform.
5.7.1.2. fhconfig.js
Just as with the mobile client application, contents of the fhconfig.json file are copied directly from the parameters given in the Connection component of the project.
5.7.1.3. Cloud Communication
The portal app, via the same SDK configuration, utilizes various $fh.cloud(…) calls to communicate with the cloud application.
5.7.1.4. Running Locally
The portal app can be ran locally by issuing the npm install followed by grunt serve commands.
5.7.1.5. Bootstrap UI Components
Where the Cordova-based mobile application used Ionic interface components to build the UI, the goal of the portal application was to build a solution not entirely tied to mobile platforms. Thus, Bootstrap was used alongside Angular to build a more browser-friendly solution that better utilizes large display space.

Where did the comment section go?
Red Hat's documentation publication system recently went through an upgrade to enable speedier, more mobile-friendly content. We decided to re-evaluate our commenting platform to ensure that it meets your expectations and serves as an optimal feedback mechanism. During this redesign, we invite your input on providing feedback on Red Hat documentation via the discussion platform.