Services That Communicate

Ray Kahn
5 min readMay 17, 2018

--

Moving on I have developed another micro-service named services-service. This service has 2 API end points that either return details of a single service associated with a provider, or a list of services for a given provider.

'use strict';
const status = require('http-status');
module.exports = (app, ServicesModel) => {
app.get('/service/:pid/:sid', (req, res, next) => {
ServicesModel.getServiceById(req.params.pid, req.params.sid).then((service) => {
res.status(status.OK).json(service);
}).catch(next);
});
app.get('/services/:pid', (req, res, next) => {
ServicesModel.getServices(req.params.pid).then((services) => {
res.status(status.OK).json(services);
}).catch(next);
});
};

Everything else about this service is similar to the one already developed in the previous exercise.

Previously we covered the following:

Here’s the start service script:

#!/usr/bin/env basheval `docker-machine env manager1`docker build -t services-service .docker run --add-host manager1:192.168.99.100 --name services-service -p 3001:3001 -d services-service

And verify that it’s in fact working:

However, the more interesting exercise would be to develop a service that uses other services that we have developed thus far. I’ll call it uber-service, which will use doctors-service and services-service using some messaging scheme.

The Uber-Service

My uber service listens on port 3002 and calls other services already developed. As you recall I developed /provider and /service end points where each returned a subset of information about a provider.

A Word Of Caution

The code here is by no means an ideal way of developing a service that calls other services. In a production environment you may want to use kafka-node or amqp-node (RabbitMQ), or even Redis, to handle inter-container communication. But for my purposes I will use request object to achieve the same result.

Calling getProviderById developed in doctors-service container
Calling getServiceById developed in services-service container

There is nothing extra-ordinary with the above code and should be straightforward to understand. Sure I should not hard-code the url but for now let’s just assume we live in an un-ideal world.

A Look At The Code

My uber service start-up script is a bit different than the ones already developed for other containers. The first thing to notice is that I am not calling any mongoDb initialization module. That’s because I don’t need access to any data/document repository. Another important piece of code is my dependency injection code.

dependency.initDependency(mediator);

What Is Dependency Injection?

Dependency injection (DI) is a software design pattern in which one or more dependencies (or services) are injected, or passed by reference, into a dependent object.

There are many benefits to DI:

  • Dependency injection makes your modules less coupled resulting in a more maintainable codebase.
  • Easier unit testing

A simple example for DI (courtesy of RisingStack article):

var express = require('express'); 
var app = express();
var session = require('express-session');
app.use(session({
# DI at work
store: require('connect-session-knex')()
}));

The need for DI is very clear: situations where you’ll need to pass object/references to your function to perform a task; that much is clear. But what happens when you need to pass multiple objects/references to a function?

Inversion Of Control (IoC)

The normal control sequence with DI is that the object finds the objects that it depends on by itself and then calls them. With IoC, the objects that are needed are handed to the object when it’s created.

The code that makes IoC possible is init.bind which returns a function with the objects serverConfig and services attached to it permanently. As a result they can be referenced by any object that needs them simply by using the initDependency object.

You may be asking yourself but what’s the big deal here? The big deal is that your code becomes more legible, maintainable and easier to test. And you have created “a single source of truth that knows about all the modules being used”.

It’s much easier to use a IoC modules specifically designed and developed to handle IoC. I use awilix for that purpose.

As can be observed, I am attaching the container (IoC container) to the request object to ensure it’s availability throughout the express ecosystem.

And as you can see I take advantage of the fact that the request object has the container object in the api file.

Let’s Build Our Latest Service

The star-service shell script is not very different from other start-service scripts we have seen already.

#!/usr/bin/env basheval `docker-machine env manager1`docker build -t uber-service .docker run --name uber-service -p 3002:3002 -d uber-service

You’ll notice that I am using port 3002 for my uber service. And the stop-service script stops the container, and removes it, along with its image.

docker stop uber-servicedocker rm uber-servicedocker rmi --force uber-service

The Dockerfile is also standard here:

FROM node:latestRUN useradd --user-group --create-home --shell /bin/false nupp && \apt-get cleanENV HOME=/home/nuppCOPY package.json npm-shrinkwrap.json $HOME/app/COPY src/ $HOME/app/srcRUN chown -R nupp:nupp $HOME/* /usr/local/WORKDIR $HOME/appRUN npm install --productionRUN chown -R nupp:nupp $HOME/*USER nuppEXPOSE 3002CMD ["npm", "start"]

Running the start-service.sh should result in service running in manager1 container. To verify:

> eval `docker-machine env manager1`
> docker ps

And we verify that our service is working as a service in manager1 docker-machine and has it’s own container.

What’s Next?

We will build an API gateway to bring all of our services under one roof. Also, if you have noticed so far we have been deploying all of our services to manager1 and that’s not what we really want. We would like to have our containers distributed to all of our virtual servers and we will tackle that after creating our API gateway container.

--

--