@mitchallen/microservice-core
v0.4.1
Published
core module for microservices
Downloads
4
Readme
@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:
- Fred George: Challenges in Implementing MicroServices (video)
- “It’s Not Just Microservices”: Fred George Discusses Technology, Process and Organisation Inhibitors
- Microservices without the Servers (Amazon)
- Adopting Microservices at Netflix: Lessons for Architectural Design
- Yelp Engineering: Using Services to Break Down a Monolith
- Scaling Gilt: from Monolithic Ruby Application to Distributed Scala Micro-Services Architecture
- How we ended up with microservices (Soundcloud)
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:
in a terminal window type:
node index.js
In a second terminal window type:
curl -i -X GET -H "Content-Type: application/json" http://localhost:8001/v1/heartbeat
Or in Chrome, browse to:
http://localhost:8001/v1/heartbeat
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