node package manager
Loosely couple your services. Use Orgs to version and reuse your code. Create a free org »

@mitchallen/microservice-core

@mitchallen/microservice-core

A node.js core module for creating microservices

The purpose of this module is to help you create a microservice - an HTTP endpoint that only does one thing.


Installation

You must use npm 2.7.0 or higher because of the scoped package name.

$ npm init
$ npm install @mitchallen/microservice-core --save

What is a Microservice?

"Microservices is an approach to application development in which a large application is built as a suite of modular services. Each module supports a specific business goal and uses a simple, well-defined interface to communicate with other modules." - Margaret Rouse

"In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies." - Martin Fowler

The basic idea

A large monolithic Web site is basically a God object. Most developers will tell you that such objects are bad. But then they go back to work on their giant Web app that takes 8 hours to compile and requires bringing the system down all night to upgrade. It may contain a dozen or even a hundred HTTP endpoints that are intertwined and difficult to maintain or deploy. If any of them break they may take down the whole site. To work on any part of the system developer may need access to all of the source code.

A microservice on the other has only one endpoint (like: example.com/api/login). It's small, self-contained, easy to maintain and can be deployed in seconds. If it is being deployed or breaks, it should have no effect on the other endpoints or the site. Developers can be restricted to only work on the code for the microservices they are assigned.

See also:


Usage

This core module can be used as a basis for a simple REST microservice or extended to provide REST + database access.

It works by letting you wrap an ExpressJS router HTTP method in a service object and passing that to the core object. The core object does all the work of setting up ExpressJS and lets you just worry about the one microservice.

Define Service Object Options

var options = {
	name: ...,
	version: ...,
	verbose: ...,
	apiVersion: ...,
	port: ...,
	method: function (info) {
	
		var router = info.router;
		
		router.[get,post,put,patch,delete] ... { 
		    ... 
		};
		
		return router;
	}

};

service.name

This can be any string. I just use the package name. If verbose is on, this is printed along with the version and port when the service is started up:

name: require("./package").name,

service.version

This can be any string. I just use the package version.

version: require("./package").version,

service.verbose

Can be true of false. If true will print name, version and port to the console on startup.

service.apiVersion

Used to define a prefix for URL's. For example if apiVersion = "/v1" then you will need to browse to "/v1/{service}".

service.port

The port to listen on.

service.method

Set the method to a function that takes one parameter (info).

method: function (info) {

    var router = info.router;
    var connection = info.connection;	// database option

    router.[get,post,put,patch,delete] ... { 
        ... 
    };

    return router;
}
info.router

The info parameter contains a pointer to a router. The router returned is an ExpressJS router. You can find out more about it here:

https://expressjs.com/en/guide/routing.html

Once you have a handle to the router you can use it to define an endpoint for handling HTTP method requests (get, post, etc.). See the router documentation for more info.

info.connection

To allow for methods that can access a database an info.connection value may also be returned.

For example, this is how you would define a service method for use with @mitchallen/microservice-dynamo:

method: function(info) {

    var router = info.router,
        dynamo = info.connection.dynamo;

    router.get( '/table/list', function (req, res) {

        dynamo.listTables(function (err, data) {
            if( err ) {
                console.error(err);
                res
                  .status(500)
                  .send(err);
            } else {
                if( info.verbose ) {
                  console.log('listTables:', data);
                }
                res.json(data);
            }
        });
    });
    return router;
}

To give you and idea of how it works, this is what a dynamo module could look like:

"use strict";

var core = require('@mitchallen/microservice-core');

module.exports = function (spec) {

  let AWS = require('aws-sdk');
  let dynamoConfig = require('./dynamo-config');
  let credentials = dynamoConfig.credentials;

  AWS.config.update(credentials);

  let connection = {
    dynamo: new AWS.DynamoDB(),
    docClient: new AWS.DynamoDB.DocumentClient()
  };

  let options = Object.assign{
    spec,
    {
    	connection: connection
    }
  });

  return core.Service(options);
};

The original service object (passed into the constructor as spec) is added passed to the core. But the connection property is also added with information specific to the Amazon Dynamo connection so that your method can also use that.

return value

Finally, your method must return the router that it was passed. That's because internally your method is being called by the ExpressJS use() method. That method will expect a router to be returned.


Pass the Options to the Service method:

core.Service(options);

Or if you want to export the returned value:

module.exports = core.Service(options);

Return Value

The object returned by the method contains a server field:

{ server: server }

It's a pointer to the express modules server. If you are familiar with express, it's the value returned by app.listen. You don't need to actually return anything. It was handy for me to use the close method in the unit tests so I wouldn't get port-in-use errors. It's also used internally when the module unexpectedly terminates.

Here is an example of how it to create it, then use the server return value to close it (checking for null omitted for brevity):

var core = require('@mitchallen/microservice-core');
// ... set options
var obj = core.Service(options);    
var server = obj.server;
server.close();

Heartbeat Example

This example can be found in the examples / heartbeat folder in the git repo.

Step 1: Create a project folder

Open up a terminal window and do the following:

$ mkdir heartbeat
$ cd heartbeat 

Step 2: Setup and Installation

You must use npm 2.7.0 or higher because of the scoped package name.

$ npm init
$ npm install @mitchallen/microservice-core --save

Step 3: Create index.js

Using your favorite text editor, create a file called index.js in the root of the project folder.

Cut and paste the contents below into it.

/*jslint es6 */

"use strict";

var core = require('@mitchallen/microservice-core');

// Define a Service object
var options = {

  // Get the name and version from package.json
  name: require("./package").name,
  version: require("./package").version,

  // Turn on console message
  verbose: true,

  // Get API version from env or use default if null
  // Used in URL, such as http://localhost:8001/{API-VERSION}/heartbeat
  apiVersion: process.env.API_VERSION || '/v1',

  // Get the port to listen on from env or use default if null
  port: process.env.HEARTBEAT_PORT || 8001,

  // microservice-core will pass an object containing an ExpressJS router.
  // Use the router to define HTTP handlers
  method: function (info) {

    // Get the Express.JS router from the options
    var router = info.router;

    // Add an HTTP GET handler to the router
    router.get('/heartbeat', function (req, res) {
        // Specifiy a JSON formatted response
        var data = {
            type: "heartbeat",
            status: "OK",
            message: 'service is running',
            timestamp: new Date(Date.now())
        };
        // Return the JSON response
        res.json(data);
    });

    // Return the router (required).
    return router;
  }
};

// Pass the options to Service
module.exports = core.Service(options);

// microservice should now be listening on the port
// Test with Chrome browser or curl command

Step 4: Test It

To test:

  1. in a terminal window type:

     node index.js
    
  2. In a second terminal window type:

     curl -i -X GET -H "Content-Type: application/json" http://localhost:8001/v1/heartbeat
    
  3. Or in Chrome, browse to:

     http://localhost:8001/v1/heartbeat
    
  4. To stop the service, in the original window press Ctrl-C.


Database Example

See the @mitchallen/microservice-dynamo module for an example of extending the core to use Amazon DynamoDB

You can find a working database example here:


Testing

To test the module, go to the root folder and type:

$ npm test

Repo(s)


Contributing

In lieu of a formal style guide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code.


Version History

Version 0.4.0

  • Bumped version number because broke backward compatibility
  • Instead of options.service { parameters } not just pass on options { parameters }
  • All parameters are now passed to the method, along with a new router and anything else that may have been added by wrapping modules.

Version 0.3.1

  • Updated example to use latest package

Version 0.3.0

  • Bumped minor version number because broke backward compatibility
  • Must now pass options to Service method instead of module.

Version 0.2.2 release notes

  • Added port in use test
  • Removed process signal handling

Version 0.2.1 release notes

  • Fixed issue with uncaught exception handler interfering with unit tests

Version 0.2.0 release notes

  • Bumped minor version number because broke backward compatibility
  • Module now returns { server: server } instead of just server.
  • Added get url test
  • Added additional error handling

Version 0.1.4 release notes

  • Added more info to the README

Version 0.1.3 release notes

  • Added examples / heartbeat demo
  • Updated README with example and background info

Version 0.1.2 release notes

  • Added .npmignore to filter out test folder, etc

Version 0.1.1 release notes

  • Ran jslint against index.js
  • Added bitbucket to repo listing
  • Added pointer to working example in README

Version 0.1.0 release notes

  • Initial release