Microservice Development using NodeJS and Oracle Application Container Cloud Service (ACCS)

Oracle Application Container Cloud service allows for microservices to be hosted using programming languages like Java SE, NodeJS and PHP. Future plans include support for Python and Ruby. An earlier post provided an overview and compared this to Oracle Container Cloud Service

The applications run in a Docker container behind the scenes and is expected to expose a service port. By configuring metadata, access can be setup to other Oracle Cloud services like database, storage, messaging etc.

Use Case

In this blog, we’ll build a weather microservice that will publish weather results for Melbourne. The weather data is retrieved from openweathermap and presented to the consumer as JSON. As the free plan allows for a maximum 2000 requests per day, this microservice will cache the result in memory and schedule weather updates every 2 hours.

Programming language

NodeJS has increased in popularity in the last few years as a server side development language to work alongside AngluarJS and other UI Javascript frameworks. Being a cloud first language with custom extensions available as packages, it’s a natural choice for cloud integration.

First, install node v6 (to match the Oracle version) and ensure that the installed folder has been added to the PATH environment variable. While any text editor may be used to write code, Atom with script package installed was used so that code can be run directly in the editor.

Code

Normally, the folder containing the NodeJS code is initialised using npm init. The resulting package.json will store the name of the package, version and dependencies for its execution.
The dependencies are initialised using npm install with the --save flag updating the package.json to store the dependencies
npm install --save express memory-cache node-schedule request

You can use the following package.json in a new workspace/folder

{
  "name": "server.js",
  "version": "1.0.0",
  "main": "server.js",
  "dependencies": {
    "express": "^4.14.0",
    "memory-cache": "^0.1.6",
    "node-schedule": "^1.2.0",
    "request": "^2.79.0"
  }
}

And initialise environment with required packages by running the following command
npm install

You’ll notice a folder called node_modules that stores the dependent packages (and their dependencies)

Next obtain an api key from openweathermap by registering for their free plan (this allows 2000 requests a day).

Create server.js and declare the packages to use and store the API KEY

var schedule = require('node-schedule');  
var request = require('request');  
var express = require('express');  
var cache = require('memory-cache');

var API_KEY = 'd528f800XXXXXXXXXXXXXXX';  

Add a function to retrieve weather and store result in a cache with key ‘melbourne’

function get_melbourne_weather() {  
    console.log('about to invoke weather service');

    // openweathermap api url to hit
    var url = 'http://api.openweathermap.org/data/2.5/weather?q=Melbourne,AU&units=metric&APPID='+API_KEY;

    // invoke openweathermap api
    request.post(
        url,
        function (error, response, body) { 
        // post invoke processing with response body
        if (!error && response.statusCode == 200) {      
            // successfully processed
            console.log('invoked weather service at '+ new Date());

            // store weather json object
            cache.put('melbourne', body);
        }
        // if error occurred, record HTTP status code
        console.log('response status: '+ response.statusCode);
    });

}

Add a recurring schedule to invoke get_melbourne_weather every 2 hours and another once-off job to populate cache on startup.

// run schedule every 2 hours, as api is refreshed every 2 hours
schedule.scheduleJob('0 0 */2 * * *', get_melbourne_weather);

// schedule once on startup in case api is invoked in first 2 hours
schedule.scheduleJob(new Date(Date.now() + 500), get_melbourne_weather);  

Frequency may be increased to 5 seconds for testing, in which case the job will be as follows:

// run schedule every 5 secs – FOR TESTING ONLY
schedule.scheduleJob('*/5 * * * * *', get_melbourne_weather);  

Next, we’ll configure Express application. This is a package that makes API development easier by providing convenience methods for common functions. process.env.PORT is an environment variable used by Oracle Application Container Cloud to set the runtime port, which can be overridden for local testing.

// initialize express application
var app = express();  
var LISTEN_PORT = process.env.PORT || 48484;

// start listening on port
app.listen(LISTEN_PORT);  

First define the root to document the service

// root to document micro service
app.get(  
    '/',
    function(req, res) {
      res.send('Melbourne Weather API: <p>GET /weather/melbourne to get the current weather');
    }
);

Configure the main API endpoint as a GET HTTP method.

// get current weather for melbourne from cache.
app.get(  
    '/weather/melbourne',
    function(req, res) {
      if(cache.size() != 1) return res.sendStatus(503);// service unavailable temporarily, try again later

      res.set('Content-Type', 'application/json');
      res.send(cache.get('melbourne'));
    } 
);

The entire file server.js will be as follows:

var schedule = require('node-schedule');  
var request = require('request');  
var express = require('express');  
var cache = require('memory-cache');

var API_KEY = 'd528f800XXXXXXXXXXXXXXX';

function get_melbourne_weather() {  
    console.log('about to invoke weather service'); 
    // openweathermap api url to hit
    var url = 'http://api.openweathermap.org/data/2.5/weather?q=Melbourne,AU&units=metric&APPID='+API_KEY;

    // invoke openweathermap api
    request.post(
        url,
        function (error, response, body) { 
        // post invoke processing with response body
        if (!error && response.statusCode == 200) {      
            // successfully processed
            console.log('invoked weather service at '+ new Date());

            // store weather json object
            cache.put('melbourne', body);
        }
        // if error occurred, record HTTP status code
        console.log('response status: '+ response.statusCode);
    }); 
}

// run schedule every 2 hours, as api is refreshed every 2 hours
schedule.scheduleJob('0 0 */2 * * *', get_melbourne_weather);

// run schedule every 5 secs – FOR TESTING ONLY
//schedule.scheduleJob('*/5 * * * * *', get_melbourne_weather);

// schedule once on startup in case api is invoked in first 2 hours
schedule.scheduleJob(new Date(Date.now() + 500), get_melbourne_weather);

// initialize express application
var app = express();  
var LISTEN_PORT = process.env.PORT || 48484;

// start listening on port
app.listen(LISTEN_PORT);

// root to document micro service
app.get(  
    '/',
    function(req, res) {
      res.send('Melbourne Weather API: <p>GET /weather/melbourne to get the current weather');
    }
);

// get current weather for melbourne from cache.
app.get(  
    '/weather/melbourne',
    function(req, res) {
      if(cache.size() != 1) return res.sendStatus(503);// service unavailable temporarily, try again later

      res.set('Content-Type', 'application/json');
      res.send(cache.get('melbourne'));
    } 
);

Run the code by issuing the following command:
node server.js

Test

You can hit the following urls in the browser to see the responses
http://localhost:48484/

http://localhost:48484/weather/melbourne

Test via Postman

Deploy to Application Container Cloud

To deploy to Application Container Cloud Service, a metadata file needs to be placed within the zip package to instruct which file to run, what runtime to use etc.

{
  "runtime":{
    "majorVersion":"6"
  },
  "command": "node server.js"
}

Package the folder contents into a zip:

  • server.js – main api code
  • package.json – NodeJS metadata file containing application name, version and dependencies
  • metadata.json – Oracle ACCS metadata file that contains runtime version and file to run
  • node_modules – folder containing dependent packages listed in package.json

Go to Oracle Application Container Cloud Service (ACCS) to deploy the NodeJS zip
https://apaas.us.oraclecloud.com/apaas/faces/aPaaSRunner.jspx

Fill in application details and upload the zip. Leave the instances at 1 and memory at minimum value 1GB. Ensure runtime version is updated to 6.3

Navigate to weather-api > Overview > In-Progress Activity section to view progress

After application is created, go to Administraton > Logs and click Get Log

Refresh screen to view zip. Click on zip and enter the ACCS credentials for Cloud Storage Credentials

To get the current weather, hit the url

https://weather-api-identitydomain.apaas.us2.oraclecloud.com/weather/melbourne

where identitydomain is the name of identity domain in your cloud subscription.
You can test the API via Postman

Lifecycle actions can be performed from Applications page

Deployment and lifecycle actions can be automated using ACCS REST API. See docs for further details.

These files are also available in github - https://github.com/rubiconred/weather-api

Using cloud-native languages like NodeJS on the Oracle Cloud is a powerful way to develop microservices. API management tools like Oracle API Platform Cloud allow these services to be governed and secured for external consumption.

Sunil Jacob

Sunil is an accomplished consultant who has worked in retail, telecom and banking domains. Over the past 10 years, he has designed and developed solutions with Oracle Middleware and Java technologies

Subscribe to Oracle PaaS, Oracle Middleware and DevOps

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!