a Microservice Microframework for node; node-xyz (part 2)

This is the second part of my story with node-xyz. It this section, I will focus on the technical aspects of xyz, explaining its architecture and design.

Other parts:

  1. The story of xyz
  2. The technical aspects (this page)

The Main Task

workerMs.register('someBusinessLogic', (payload, respond) => {
// calculate some stuff
// fetch some data
respond.jsonify({message: "task done!"})

and the other node, probably located in another host half the way around the globe:

service: 'someBusinessLogic',
payload: {someKeys: 'someValues'},
(err, msgResp) => {
console.log(msgResp) // {message: "task done!"}

Albeit, there is a LOT more to it than this. In order to understand them we should first have glimpse at the architecture.

The Architecture

xyz has a layered structure with configurable middlewares acting as the pipeline conveying instructions and messages between layers.

The module is divided into three layers:

  1. the xyz layer: high level API functions for the developer to use. It technically does nothing other than constructing other layers and calling them with appropriate parameters.
  2. the Service Repository layer: This layer manages local services that its local node is exposing and explores foreign services when it needs to send a message to them.
  3. the Transport layer: a complement for the Service Repository that does the actual job of sending messages over the network and listens for new incoming messages.

It looks something like this:

What gives this architecture its power are the middlewares that connect these layers one another. In a simple form, I can emphasize their importance like this:

No message is allowed to enter or leave without going through middlewares.

I can now complete the last image like this:

Where each white dashed arrow is actually a set of functions inside a middleware stack, not a direct invocation.

The fact that the “functions injected into these middlewares are modifiable by the developer” and that “all messages will go through a middleware” indicates that the entire behavior of the system is also modifiable by the developer. Some examples of this are explained in the following sections.

More than Architecture

Transport Independency

Open Access

These two attributes, Open Access and Transport Independency, specifically enabled me to do more decomposition, meaning that more functionality was moved out from the core module. These functionalities were then re-added again as plugins (or what we call Bootstrap Functions). As an example, the service discovery and health checking mechanism was at first fix and built it. After a while, I began to question myself why should it be? As a plugin, it still has all of the required access rights and it brings the benefit of more configurability. As a result, in later versions of xyz, a node could choose to have a specific Service Discovery mechanism, or even choose not to have one. Wondering how might that be useful?

Service Discovery is only useful for when you want to have a dynamic cluster of node. What if for a specific test or application, you know the address of all nodes beforehand and you are sure that they won’t change? Furthermore, different applications with different scale are best to work with different Service Discovery mechanisms. A small cluster of 5 nodes could work with a robust heartbeat based (which is quite inefficient) algorithm that uses reliable HTTP for probing, while this approach is by no mean efficient in a larger cluster and lightweight approach should be used.

Consequently, the current xyz source code is consisted of:

  1. xyz-core, which is basically everything that I just explained.
  2. xyz-cli which will not be explained here but I am sure that you can guess what it is.
  3. numerous middlewares and plugins that used to be a part of xyz-core but now they aren’t.

xyz in Action

Changing Transport Layer

Adding Authentication or Encryption

xyz does not enforce any application dependent procedure.

Yes Yes. I know that 99% of the time there should be an authentication for a service . But let’s respect that 1% and decide things like authentication to be optional in xyz.

This can be quite useful because it allows the framework to be used for much wider range of application. You could implement a public service repository in the cloud with xyz, which should not have any strict authentication, while being able to implement an enterprise application with significant authorization roles. How?

Adding features like authentication can be as simple as injecting two middlewares. As said before, everything that is important in xyz happens inside middleware functions. One can simply:

  1. add a function to an outgoing middleware that adds authorization tokens to the message
  2. add a function to a server middleware that checks that authorization token and drops the message if it doesn’t have it.

The output will look like this:

Having a layered architecture makes this feature even more valuable by drawing a clear line between each layer’s responsibility. As an example, in the above scenario, two arbitrary Transport layer middlewares are responsible for adding the token and checking it. The Service layer doesn’t know about this authentication, nor does it care about it. If a message is sent up from the Transport layer to the Service layer it means that it is verified. If it is not, it won’t even reach the Service layer. This separation of concerns makes the development of middlewares clearer. Similar to microservice spec., middleware functions should also do a specific, independent task that can be mixed and composed with other middleware functions.

Adding a Message Queue

By default messages reach the Transport layer and are sent to the Service layer via a middleware function immediately. Two components are required to easily make xyz cooperate with a queue.

  1. a new middleware function will replace to one that was sending messages to the Service layer. The new one will store messages inside a queue instead of sending them to Service layer.
  2. An independent component will always check the queue for messages and will send them to the service layer. How is it possible? Thanks to the open access policy of the framework. Any external plugin can use the Service Layer, just as the xyz layer that is using it internally.

Send Strategy

  1. Those who resolve the path of the service: these functions will look at the message and carefully choose a destination node that can respond to it. This is particularly useful when exposed services are more like a Task and offered by multiple nodes, which is not that uncommon. Suppose that we know that five foreign nodes expose a specific task as a function and we just want the message to be sent to one or few of them. We don’t really care about the receiver or the actual physical location of the destination. We just one someone to do this task. Existing Sent Strategies that follow this path are: sendToAll, FirstFind, Round Robin.
    These functions will also utilize the fact that the identifier that they are basing their judgment on is a Path, meaning that it can be quite dynamic by allowing wildcards (you can send a message using sentToAll to /aTaskGroup/* which will invoke /aTaskGroup/task1, /aTaskGroup/task2 and /aTaskGroup/task3) or services with deep and well structured paths.
  2. Those who simply ignore that message path and send it according to other parameters. As an example, BroadcastLocal and BroadcastGlobal are strategies that lie in this category.

The aim of allowing this degree of flexibility is to allow the developers to choose the functionalities that they need and ideally, share them with other developers by making their code open-source.

note: some other features that I find cool but won’t explain them here to keep the text short: Round Robin Load Balancing, xyz-cli

Recalling the Objectives

Support the portion of the microservice spec. that is required for almost all projects

I think it does enough. Anything more would’ve been application dependent and should be a plugin.

Stay minimal

I think it turned out to be more low level than minimal. The term minimal is quite ambiguous to be honest. But I can see that it actually is low level. A friend of mine, after hearing me present xyz, said that it is more appropriate for writing high level frameworks on top of it than it is for direct development.

Be as clear and lucid as possible

It sure is. If you follow the tutorials, You will immediately get the feeling that you can see all the way through the system. Nothing is hidden. Nothing is a secret. You can sense how messages flow through middlewares and get modified by them until they reach the last middleware of the Transport layer and actually leave your computer.

Be as configurable as possible

Bothe middlewares and bootstrap functions (leveraging from the Open Access attribute) are designed explicitly for this purpose. Theoretically, they are enough to ensure that an application written in xyz can do anything possible. I still haven’t found a counterexample. Drop me a comment if you do!

Use standard javascript. Have minimum dependencies

xyz is written pure by javascript supported by node V6 and above (and can be ported to >4 with minimum effort) no build required. no Babel.

I initially had this objective in mind because even though i absolutely love javascript, I hate the fact that a an overwhelming number of dev tools have to be configured to get things working, only to crash with the next update since everything is moving so fast. I wanted something simple to start with and upgrade it later, if required.

One possible upgrade is that I am currently tempted to draw a red line over this objective and move the source code to TypeScript before it’s too late, mainly because debugging would be easier with strong typings.

Document early and often

Currently, all of the important concepts of xyz (micro)framework are documented in its website. All repositories have sufficient information in their README. the only part that still needs some work is the API Ref.


  1. Some aspects of xyz are inspired by Akka since I was working with it at the time. Path based services (instead of Pattern Matching) is an example of this inspiration.

The Future Ahead

Full Stack Web Developer. Part time geek trying to understand how Distributed Systems work.