Creating a Microservice

Any theatrical performance has actors—the people in front of you, making noise and performing actions. But there are also the stagehands backstage making sure that all the actors get what they need, and generally making sure the production goes smoothly. In Finsemble, microservices are those stagehands: operating invisibly in the background, providing central infrastructure to the components that need it.

Generating a microservice template with the CLI

The easiest way to set up your microservice is to use our command-line interface. If you have the CLI, run the following: finsemble-cli add service <microserviceName>

This will create two template files, microserviceName.js and microserviceName.html, in the folder /src/services/microserviceName/. It will also register your microservice in /configs/application/services.json and ensure that it is built by Webpack.

Accessing Client APIs from Finsemble microservices

Unlike components, microservices do not have the FSBL object injected into them, which normally provides you with access to our pre-initialized API clients. Hence, any clients that you need to use in a microservice must be imported and initialized.

const Finsemble = require("@chartiq/finsemble");
const RouterClient = Finsemble.Clients.RouterClient;
const Logger = Finsemble.Clients.Logger;
Logger.start();
const DistributedStoreClient = Finsemble.Clients.DistributedStoreClient;
DistributedStoreClient.initialize();
const HotkeyClient = Finsemble.Clients.HotkeyClient;
HotkeyClient.initialize();

The Base Service

During start-up, Finsemble needs to ensure that the clients and microservices needed are all available before it tries to start your microservice. Similarly, Finsemble may need to ensure that your microservice has access to another (such as the Storage Service) as it shuts down. Note: The Central Logger is a good tool for discovering the names of clients and services utilized by Finsemble.

To orchestrate start-up and shutdown we provide the Base Service prototype which microservices can extend:

function myService() {
    //service functions
    this.myFunction = function (args, callback) {
        ...
    };
    return this;
}
const baseService = Finsemble.baseService;
myService.prototype = new baseService({
    startupDependencies: {
        clients: ["distributedStoreClient"],
        services: ["authenticationService", "routerService"]
    },
    shutdownDependencies: {
        services: ["distributedStoreService", "storageService"]
    }
});

The Base Service constructor takes a single params object with two properties: startupDependencies and shutdownDependencies. Both clients and microservices can be listed as start-up dependencies but only microservices can be listed as shutdown dependencies. If a microservice or client is listed as a start-up dependency, your microservice will not start until that dependency is available. We recommend that you should depend on the client for a microservice, rather than the microservice itself, as the client itself will either depend on the microservice or support the queuing of requests until it is up.

When all dependencies are satisfied, the onBaseServiceReady event will be emitted and you can begin setting up your microservice.

let serviceInstance = new myService('myService');
serviceInstance.onBaseServiceReady(function (callback) {
    ...
});

Using your Microservice

Microservices can communicate with components and other microservices through the Router Client. For example:

serviceInstance.onBaseServiceReady(function (callback) {
    ...
    RouterClient.addResponder("YF server", function(error, queryMessage) {
        if (!error) {
            if (queryMessage.data.query === "myFunction") {
                queryMessage.sendQueryResponse(null, serviceInstance.myFunction());
            } else {
                queryMessage.sendQueryResponse("Unknown query function: " + queryMessage, null);
                Logger.error("Unknown query function: ", queryMessage);
            }
        } else {
            Logger.error("Failed to setup query responder", error);
        }
    });

    Logger.log("myService ready");
    callback(); 
});

A simple client may be created by creating another file in the same directory as your service, e.g., myServiceClient.js:

const Logger = FSBL.Clients.Logger;
const RouterClient =  FSBL.Clients.RouterClient;

export function myFunction(cb) {
    Logger.log("myFunction called");
    RouterClient.query("myService functions", { query: "myFunction" }, function (err, response) {
        Logger.log("myService.myFunction response: ", response.data);
        if (cb) {
            cb(err, response.data);
        }
    });
};

You can then import or require in any component that will need to access the microservice, e.g.:

import {myFunction} from '../../services/myServiceClient';

Then, you can use it (asynchronously) in the component as if it were a local function:

myFunction(function(err, response) {
    if (err) {
        Logger.error("Failed to call myFunction!", err);
    } else {
        Logger.log("called myFunction: ", response);
    }
});

check   The CLI makes it easy to create new microservices. Any clients that you need to use in a microservice must be imported and initialized.
 


Further reading

The Base Service is used to create a new microservice.

Finsemble microservices are communicated with via the Router.