How to Build a Microservices Architecture With Node.Js to Achieve Scale?
How to Build a Microservices Architecture With Node.Js to Achieve Scale?
To run the application smoothly, it is essential to convert the large, homogeneous structure into small, pieces of program— do this with Node.js.
Join the DZone community and get the full member experience.
Join For FreeIntroduction
Building real-world applications in the JavaScript language requires dynamic programming, where the size of the JavaScript application grows uncontrollably. New features and updates get released, and you need to fix bugs to maintain the code.
To execute this, new developers need to be added to the project, which becomes complicated. The structure of modules and packages is unable to downsize and simplify the application. To run the application smoothly, it is essential to convert the large, homogeneous structure into small, independent pieces of programs. Such complexities can be resolved effortlessly when JavaScript applications are built on microservices, more so with the Node.js ecosystem.
What Are Microservices?
In software application development, microservices are a style of service-oriented architecture (SOA) where the app is structured on an assembly of interconnected services. With microservices, the application architecture is built with lightweight protocols. The services are finely seeded in the architecture. Microservices disintegrate the app into smaller services and enable improved modularity.
Compared to its predecessor, the monolithic architecture, microservices are hands down more beneficial. You don’t need to stuff all software components and services in one big container and pack them tightly. With microservices, you can build an app with:
- Greater flexibility.
- High scalability.
- Continuous development.
- Systematic data organization.
- Time optimization.
- Reliability.
Building JavaScript applications on microservices help you focus on developing monofunctional modules with clearly defined operations and precise interfaces. The application development process becomes more agile, and the challenges of continuous testing are mitigated.
When you build applications on a monolithic architecture, the entire application needs to be deployed with every update. On the other hand, microservices have no dependency on the type of framework, technique or programming language being used to build them. Your ability to release REST-ful APIs for communication and other services is the only requisite for microservice architecture.
The Node.js Platform
When it comes to building JavaScript microservices, a majority of developers prefer the Node.js platform. In a JavaScript engine, Node.js is a cross-platform, open-source runtime environment (RTE) used for building networking and server-side applications. Written in JavaScript, Node.js is executed on Microsoft Windows, OS X, and Linux operating systems within the Node.js runtime.
Reasons for Using Node.Js for Building Microservices
A rich database of multiple JavaScript modules provided by Node.js simplifies the application development at a great scale. Software architects prefer Node.js as a technology partner for developing JSON API-based applications, I/O bound applications, data streaming applications, single-page applications, and data-intensive real-time applications.
Key Benefits of Node.js
- Single-threaded: With event looping, the server uses a non-blocking mechanism to respond.
- Super-fast: Codes are executed quickly on the V8 JavaScript Engine.
- Event-driven: ‘Events of Node.js’ is a notification system that enables the application server to capture the response of the previous API call.
- Buffer-less: There is no buffering as data is simply released in chunks.
- Asynchronous: The non-blocking, non-synchronous Node.js libraries move to the next API and do not await the return data of the previous API.
- Highly-scalable: Servers can handle as many requests as coming their way.
- Licensed: The program is authorized under a software license.
Microsoft, PayPal, Uber, eBay, and Yammer are leading companies actively using Node.js application development for their projects.
Building Microservices With Node.js
To build microservices for real-world applications with Node.js, a basic understanding of JavaScript programming is important. The steps involved in developing microservices with Node.js showcase how working applications in our hyperconnected world can perform superbly when constructed with a functional combination of multiple, unique APIs.
To understand the process better, let’s use Node.js to build a microservice for connecting external APIs. As the development progresses, improvements will be made to make the microservice more responsive and cost-effective.
1. Assess the Business Need
To build this microservice, let’s assume that a business requires a service that identifies two ZIP codes and provides the distance between them. The distance is calculated in miles. You’ll need to use validation techniques for ZIP code identification and distance calculation. The requirement wants the service to configure external API calls. And, it is essential to quickly duplicate calls and make the process more cost-efficient. For this, an internal cache can be implemented.
2. Initialization
To begin with, install Node.js on your computer or workstation. Installing the Node.js
8.1.1 version is recommended. To download the latest version of Node.js, visit Nodejs.org.
NPM (Node.js packet manager) is included in the installation suite. There are several stages where you need to use the NPM. For building this microservice, NPM will be used for launching the project, loading the dependencies, and executing the service.
To initialize the project,
- Open the Node.js platform.
- Go to the root folder.
- Run the command: $ npm init.
Executing the command initiates the walk-through for package.json file creation. By creating the package.json, the project establishes its foundation. The name and version of the service are entered, or you can choose from the defaults and update later.
The microservice will be built on two primary packages — Request and Express.
The Request package enables the microservice to set up a connection with third-party, web-based APIs.
The Express package is a structure for Node applications used for supporting the Node.js foundation of the microservice.
To add these packages to the package.json file,
- Enter the command: $ npm install express request.
- At the end of the command, add: —save.
- Run the command.
The npm init command has developed a structure of files and folders for creating the microservice. The primary file is called server.js. API support files are stored in the API folder. Third-party API connection logic is saved in the service folder. Other key folders include node_modules, package-lock.json, and package.json.
The Express and Request packages are saved to this structure as dependencies in the node_modules folder.
Now, the coding for microservice can begin.
3. Setting Up the Server
The first part of the coding involves building a server that recognizes and accepts the requests. For this, you need to begin with the server.js file, which is the primary file of the project. The code used to create server.js file goes like this:
xxxxxxxxxx
var express = require('express') var app = express(); var port =
process.env.PORT || 3000; var routes = require('./api/routes'); routes(app);
app.listen(port,function(){ console.log('Server started on port: ' + port); });
The next step would be specifying the routes for response transmission. The server created earlier assigns routes that ensure all requests are processed.
4. Specifying the Routes
Defining the routes is a critical stage in microservice development. The routes are defined by two end-points for dispatching and accepting requests.
In this project, the Express package is added to the sever.js file and is further used to build a new app object. In JavaScript development, an app object is responsible to set or remove attributes from the application scope. Now, the port is specified, and the system properties are configured according to the process object. The role of the process object is to execute the process by performing the input, running the specified process, checking the output status of the process, and ending the process thereafter.
The default port specification is valued at 3000. By specifying PORT as the environment variable, you can choose to define the port number (value) based on the system running this application.
In the next stage, a route object is transmitted to the API folder from the routes.js file. While doing so, the app object (created earlier) is carried forward with the routes object. This allows the app to start wiretapping the port that was defined. As this process gets completed, the app displays a message to the console.
Each route defined to the server needs to assigned to a controller object target. To build the controller object, use the two end-points and an about endpoint that provides the application information. For this project, both the ZIP codes will be used as the two parameters defining the path for distance endpoints. When the target is assigned, the endpoint displays the distance between the two ZIP codes, calculated in miles.
xxxxxxxxxx
'use strict'; var properties = required('../package.json') var distance =
require('../service/distance'); var controllers = { about: function(req,res){
var aboutInfo = { name:properties.name, version: properties.version }
res.json(aboutInfo); }, get_distance:function(req, res){ distance.find(req,
res, function(err,dist){ if(err) res.send(err); res.json(dist); }); }, };
New JavaScript versions enforce the security for coding practices by employing the use of strict directive. To state the functions inside a module, module.exports is used. This way, the stated functions are can be used by keeping them available in another file which includes the routes module. The routes module specifies the routes for the Express package and is imported from the server.js folder.
At this stage of the process, two routes are added to the microservice. The GET requests are transmitted with the first route on the /about endpoint. The function of the controller responds to these requests. The second route processes the GET requests on /distance endpoint, and the controller use the get_distance function to handle them. The two parameters, “zipcode1” and “zipcode2”, are specified respectively.
The controller object that handles these requests is created in the next stage. It is important to exclude the controller object from the server.js file or the routes.js file functions so that all files can access it.
5. Building the Controller
Adding controller logic to the microservice equips it with some interesting functions. A controller object interprets user intentions and actions and communicates the new or modified data to process objects.
For this microservice, a controller object with two properties needs to be created in the controller file. The two properties are nothing but the functions of handling the requests received from the routes module.
x
'use strict'; var properties = required('../package.json') var distance =
require('../service/distance'); var controllers = { about: function(req,res){
var aboutInfo = { name:properties.name, version: properties.version }
res.json(aboutInfo); }, get_distance:function(req, res){ distance.find(req,
res, function(err,dist){ if(err) res.send(err); res.json(dist); }); }, };
module.exports = controllers;
By using this code, the properties controller object is created. It references the package.json file created at the first step of this project. With this object, the process object can import and use the informational contents of the package.json file.
The code also has two distinct parts — the about the function and the get_distance function. The first functionality accepts response objects and requests. For executing the about functionality, a new object from the package.json file is returned as a response object.
The get_distance functionality syncs the distance module with the find function (also called the callback function). It accepts the distance objects as well as the error objects. In case of errors, the find function sends back the response object.
6. Establishing the External API Call
Now that the controllers are added, an external service is ready to be executed. This is the final part of the microservice development with Node.js. Here, the third-party API call is handled by the API file. To get the API key, you can get is free from ZIPCODEAPI.com. To make the external call, set an expired test key as the default key.
xxxxxxxxxx
var request = require('request'); const apiKey = process.env.ZIPCODE_API_KEY ||
"(Your API key)" const zipCodeURL = 'https:,'/mwa.zipcodeapi . com/rest/'; var
"distance = { find: function(reg, res, next) { request (zipCodeURL + apikey
+'/distance.json/' + req.params.zipcode1 + '/' + req.params.zipcode2 +
'/mile', function (error, response, body) { if ( !error && response.
statusCode = 200) { response =
JSON.parse(body); res.send(response); } else { console.log(response.statusCode
+ response.body);
res.send({distance: -1}); } }); } }; module.exports = distance;
The code runs the Request package to make the external HTTP request, which needs to be updated for testing error conditions. The code enables the find function to accept the parameters for objects namely—request, response, and next. URL of the service is accepted by the request object. The callback function handles the response object.
“HTTP Status code 200” is the status of the response object, provided there are no errors. The body of the response is parsed out and returned. Instead of forwarding directly, parsing the response allows the response to be handled with maximum efficiency.
7. Execution
Eventually, the distance object is exported, and the controller can represent the concrete instances and functions of the external API calls as needed. To wrap up the execution of this microservice, the code needs to be reviewed for typos in the command.
Challenges of Building Microservices in Node.js
Before creating microservices with Node.js, you should also be aware of certain challenges that come with the programming platform. Some of the critical drawbacks of building JavaScript microservices with Node.js include:
- Unable to process CPU bound tasks: Node.js is single-threaded and cannot compute CPU-heavy tasks. The application experiences performance bottlenecks as the whole CPU bandwidth gets used for processing heavy requests. An experimental feature in Node.js 10.5.0 update has introduced multithreading to fix this challenge.
- Poor NPM tools: Besides the core NPM tools, a majority of tools on the Node.js platform are not properly tested or documented. The registries are unorganized, and the open-source ecosystem is not adequately supervised by Joyent, the creators of Node.js.
- Callback hell: The callback function is heavily used to run each task. When the tasks exceed a limit, the situation leads to a “callback hell” and maintaining the code becomes near-impossible. The code can, however, be simplified and re-factored to lower the impact of the asynchronous nature of Node.js on callback functions.
- Lack of Node.js developers: There aren’t as many Node.js developers when compared to the presence of JavaScript developers. The growing demand for Node.js remains unsatisfied due to the lack of professional experience of developers on this relatively new programming technology.
Despite these drawbacks, Node.js is still observed as a suitable environment to build real-world applications and JavaScript microservices. The basic distributed system of microservice architecture with Node.js shows you how to separate applications build communications between services. The RESTful APIs process the data and maintain the code towards the deployment of each microservice. In the years to come, Node.js will be actively used in building mature, open-source tools and developing microservices for enterprise-level software.
Further Reading
Understanding Microservices Choreography Using RabbitMQ and Node.js
Published at DZone with permission of Sagar Tambe. See the original article here.
Opinions expressed by DZone contributors are their own.
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}