article

Service to Service communication with Hemera and NATS

13 Feb 2018 | 5 min read

pattern/design file

Hemera 5 has just been released and we’d like to give you an overview of why message queues are great for your Node.js application and how you can apply them easily in your code to optimise performance and maintainability.

Projects get bigger all the time, features are added along the way and you might end up with a big application (monolith). The application becomes difficult to understand and modify, difficult to implement new features and over time, code quality decreases and development speed slows down. To avoid this issue from the start, you can structure your application by Domain (Domain Driven Design), using separate services (needn’t necessarily be *micro *services) for separate logical clusters or business purposes. For example: Products, Users and the Shopping Cart. Each service has different needs and requirements: you might have a database for millions of products and highly volatile eCommerce transactions. It would not make much sense to use the same database and system resources for such different purposes and you can save a lot of money by optimising usage. Service-based architecture also helps to exchange parts in your technology stack to evolve with your requirements.

Coming from a single application, you would probably import/require products.js and use something like products.list() to retrieve a list of products, maybe even enhanced with pagination and other metadata. products.js is maintained by another developer and you trust it to work as you expect it. There are several ways to do this in a service-based architecture. We decided to use NATS as message queue system. It’s very simple and incredibly fast. There’s an official NATS docker image that you can pull in docker-compose.

There are alternatives like HTTP, RabbitMQ etc. which all have their pros and cons which we won’t discuss in this article.

NATS Remote Functions with Hemera

Hemera is a Node.js toolkit for NATS and simplifies messaging in distributed systems. The pattern we use is called RPC — Remote Procedure Call (named Request & Reply in Hemera). Instead of the applications internal products.list() example above, the call is wrapped in a hemera function and executed by a different service. The behaviour is exactly the same, you can use async/await, Promises or Callbacks, only the function call changes a little bit:

hemera.act({topic: “users”, cmd: “list”})

To register the function in the distributed system, you add the topic and command to the messaging system:

hemera.add({topic: “users”, cmd: “list”}, function() { … })

“act” calls for a function, “add” registers a function on the queue.

On each service, you add available functions: users.list(), users.get(id), product.update({}) etc. From the API (or anywhere else) you call (act) that function with the required parameters.

The full command to register a function on the queue with hemera requires a topic, a command and the function. Further attributes are optional:

Congratulations, you wrote your service-to-service Hello World App. Let’s move on.

Joi

A neat addition to mention at this point is Joi Schema Validation. As the name says, a defined schema is used for input validation. The function above doesn’t accept “Hello” as value for a but instead throws an error. There’s no need to re-validate or check the input parameters, for example if (!req.a) {...}, if (!req.b) {...} , which is great. We’re working on a way to automatically extract the Joi validation of a Hemera function into a JSDocs for better documentation.

Improved Hemera handlers

The first step when writing something other than “Hello World” is to use a named function instead of the inline function above. To keep definition and implementation together, we export both from a single file:

In the main file (ie. index.js), those are added to the queue:

hemera.add(getProduct.params, getProduct.handler)

This pattern has worked well for us, the functions are all registered in one place. And the definition, validation rules and implementation are in a single file, which makes it easy to focus on the function. There’s no need to switch through multiple files.

Demo Time

Service to Service communication with Hemera and NATS

from Zentered on Vimeo .

To demonstrate how all this works together, we have prepared a repository with all the necessary files here: hemera-nats-demo. Every service runs in a docker container, all services connect to the central NATS system, register their functions and can then be called by the API.

sequence diagram of a distributed function callsequence diagram of a distributed function call

Summary

NATS and hemera have helped us simplify a monolithic system into many different, small services. We subsequently increased our productivity, as developers are more confident while working on smaller services. Decisions and changes result in less technical impact and more user value.

Special thanks to the team behind NATS and Dustin Deus, the lead author of hemera.

We will write about unit and integration testing of distributed services in the next article. Follow us to get notified about the publication.

If you have any questions or comments, please reach out on Twitter or start a discussion on GitHub.

Patrick Heneise

Chief Problem Solver and Founder at Zentered

I'm a Software Enginer focusing on web applications and cloud native solutions. In 1990 I started playing on x386 PCs and crafted my first “website” around 1996. Since then I've worked with dozens of programming languages, frameworks, databases and things long forgotten. I have over 20 years professional experience in software solutions and have helped many people accomplish their projects.

pattern/design file