Giter Club home page Giter Club logo

cote's Introduction

cote

cote — A Node.js library for building zero-configuration microservices

npm version Build Status Coverage Status dependencies Status GitHub license FOSSA Status

cote lets you write zero-configuration microservices in Node.js without nginx, haproxy, redis, rabbitmq or anything else. It is batteries — and chargers! — included.

Join us on cote Slack for anything related to cote.

Features

  • Zero dependency: Microservices with only JavaScript and Node.js
  • Zero-configuration: no IP addresses, no ports, no routing to configure
  • Decentralized: No fixed parts, no "manager" nodes, no single point of failure
  • Auto-discovery: Services discover each other without a central bookkeeper
  • Fault-tolerant: Don't lose any requests when a service is down
  • Scalable: Horizontally scale to any number of machines
  • Performant: Process thousands of messages per second
  • Humanized API: Extremely simple to get started with a reasonable API!

Develop your first microservices in under two minutes:

in time-service.js...

const cote = require('cote');
const timeService = new cote.Responder({ name: 'Time Service' });

timeService.on('time', (req, cb) => {
    cb(new Date());
});

in client.js...

const cote = require('cote');
const client = new cote.Requester({ name: 'Client' });

client.send({ type: 'time' }, (time) => {
    console.log(time);
});

You can run these files anyway you like — on a single machine or scaled out to hundreds of machines in different datacenters — and they will just work. No configuration, no third party components, no nginx, no kafka, no consul and only Node.js. cote is batteries — and chargers — included!

Microservices case study

Make sure to check out the e-commerce case study that implements a complete e-commerce application with microservices using cote. It features;

  • a back-office with real-time updates for managing the catalogue of products and displaying sales with a RESTful API (express.js)
  • a storefront for end-users with real-time updates to products where they can buy the products with WebSockets (socket.io)
  • a user microservice for user CRUD
  • a product microservice for product CRUD
  • a purchase microservice that enables users to buy products
  • a payment microservice that deals with money transactions that occur as a result of purchases
  • Docker compose configuration for running the system locally

cote plays very well with Docker, taking advantage of its network overlay features. The case study implements a scalable microservices application via Docker and can scale to multiple machines.

Table of Contents

  1. Motivation
  2. Getting started
    1. Introduction to cote
    2. Installation
    3. Using cote for the first time
    4. Implementing a request-response mechanism
      1. Creating a requester
      2. Creating a responder
    5. Tracking changes in the system with a publish-subscribe mechanism
      1. Creating the arbitration service
      2. Creating a publisher
      3. Creating a subscriber
  3. Components Reference
    1. Requester
    2. Responder
    3. Publisher
    4. Subscriber
    5. Sockend
    6. Monitor
    7. Monitoring Tool
  4. Advanced Usage
    1. Environments
    2. Keys
    3. Namespaces
    4. Multicast address
    5. Broadcast address
    6. Controlling cote with environment variables
  5. Deploying with Docker Cloud
  6. Using centralized discovery tools
  7. FAQ
  8. Contribution
  9. License

Motivation

Tomorrow belongs to distributed software microservices. As CPU performance is heavily dictated by the number of cores and the power of each core is already at its limits, distributed computing will decide how your application performs. Distributed systems Microservices also pose great architectural benefits such as fault-tolerance and scalability.

Components of such a distributed system microservices should be able to find other components zeroconf and communicate over a set of conventions. Sometimes they may work as a cluster, may include a pub/sub mechanism, or a request/response mechanism.

cote brings you all the advantages of distributed software microservices. Think of it like homing pigeons.

Getting Started

Introduction to cote

cote allows you to implement hassle-free microservices by utilizing auto-discovery and other techniques. Typically, in a microservices system, the application is broken into smaller chunks that communicate with each other. cote helps you build such a system by providing you several key components which you can use for service communication.

In a way, cote is the glue that's most necessary between different microservices. It replaces queue protocols and service registry software by clever use of IP broadcast/IP multicast systems. It's like your computer discovering there's an Apple TV nearby. This means, cote needs an environment that allows the use of IP broadcast or multicast, in order to scale beyond a single machine. Most bare-metal systems are designed this way, however, cloud infrastructure like AWS needs special care, either an overlay network like Weave, or better yet, just, Docker — which is fortunately the way run all of our software today anyway. That's why Docker is especially important for cote, as it enables cote to work its magic.

cote also replaces HTTP communication. Microservices architecture is meant for hundreds of internal services communicating with each other. That being the case, a protocol like HTTP is cumbersome and heavy for communication that doesn't need 90% of HTTP's features. Therefore, cote uses a very light protocol over plain old TCP sockets for communication, making it fast, effective and most importantly, cheap.

Installation

cote is a Node.js library for building microservices applications. It's available as an npm package.

Install cote locally via npm:

npm install cote

Using cote for the first time

Whether you want to integrate cote with an existing web application — e.g. based on express.js as exemplified here — or you want to rewrite a portion of your monolith, or you want to rewrite a few microservices with cote, all you need to do is to instantiate a few of cote's components (e.g. Responder, Requester, Publisher, Subscriber) depending on your needs, and they will start communicating automatically. While one component per process might be enough for simple applications or for tiny microservices, a complex application would require close communication and collaboration of multiple microservices. Hence, you may instantiate multiple components in a single process / service / application.

Implementing a request-response mechanism

The most common scenario for applications is the request-response cycle. Typically, one microservice would request a task to be carried out or make a query to another microservice, and get a response in return. Let's implement such a solution with cote.

First, require cote;

const cote = require('cote');

Creating a requester

Then, instantiate any component you want. Let's start with a Requester that shall ask for, say, currency conversions. Requester and all other components are classes on the main cote object, so we instantiate them with the new keyword.

const requester = new cote.Requester({ name: 'currency conversion requester' });

All cote components require an object as the first argument, which should at least have a name property to identify the component. The name is used mainly as an identifier in monitoring components, and it's helpful when you read the logs later on as each component, by default, logs the name of the other components they discover.

Requesters send requests to the ecosystem, and are expected to be used alongside Responders to fulfill those requests. If there are no Responders around, a Requester will just queue the request until one is available. If there are multiple Responders, a Requester will use them in a round-robin fashion, load-balancing among them.

Let's create and send a convert request, to ask for conversion from USD into EUR.

const request = { type: 'convert', from: 'usd', to: 'eur', amount: 100 };

requester.send(request, (err, res) => {
  console.log(res);
});

You can save this file as client.js and run it via node client.js.

Click to see the complete client.js file.

const cote = require('cote');

const requester = new cote.Requester({ name: 'currency conversion requester'});

const request = { type: 'convert', from: 'usd', to: 'eur', amount: 100 };

requester.send(request, (err, res) => {
  console.log(res);
});

Now this request will do nothing, and there won't be any logs in the console, because there are no components to fulfill this request and produce a response.

Keep this process running, and let's create a Responder to respond to currency conversion requests.

Creating a responder

We first instantiate a Responder with the new keyword.

const responder = new cote.Responder({ name: 'currency conversion responder' });

As detailed in Responder, each Responder is also an instance of EventEmitter2. Responding to a certain request, let's say convert, is the same as listening to the convert event, and handling it with a function that takes two parameters: a request and a callback. The request parameter holds information about a single request, and it's basically the same request object the requester above sent. The second parameter, the callback, expects to be called with the actual response.

Here's how a simple implementation might look like.

const rates = { usd_eur: 0.91, eur_usd: 1.10 };

responder.on('convert', (req, cb) => {
    cb(null, req.amount * rates[`${req.from}_${req.to}`]);
});

Now you can save this file as conversion-service.js and run it via node conversion-service.js on a separate terminal.

Click to see the complete conversion-service.js file.

const cote = require('cote');

const responder = new cote.Responder({ name: 'currency conversion responder' });

const rates = { usd_eur: 0.91, eur_usd: 1.10 };

responder.on('convert', (req, cb) => {
    cb(null, req.amount * rates[`${req.from}_${req.to}`]);
});

As you run the service, you will immediately see the first request in client.js being fulfilled and logged to the console. Now you can take this idea and build your services on it.

Notice how we didn't have to configure IP addresses, ports, hostnames, or anything else.

Note: By default, every Requester will connect to every Responder it discovers, regardless of the request type. This means, every Responder should respond to the exact same set of requests, because Requesters will load-balance requests between all connected Responders regardless of their capabilities, i.e, whether or not they can handle a given request.

If you have multiple Responders with varying response handlers, you will experience lost requests. In cote, this separation between responsibilities is called segmentation, or partitioning. If you wish to segment your requests in groups, you can use keys. Check out keys for a detailed guide on how and when to use segmentation.

Tracking changes in the system with a publish-subscribe mechanism

One of the benefits of a microservices approach is its ease of use as a tool for tasks that previously required serious infrastructural investments. Such a task is managing updates and tracking changes in a system. Previously, this required at least a queue infrastructure with fanout, and scaling and managing this technological dependency would be a hurdle on its own.

Fortunately, cote solves this problem in a very intuitive and almost magical way.

Say, we need an arbitration service in our application which decides currency rates, and whenever there's a change within the system, it should notify all the instances of conversion services, so that they facilitate the new values.

Of course, the arbitration service would be API driven, and would receive the new rates over another request so that for example an admin can enter the values through a back office application. The arbitration service should take this update and basically forward it to every conversion service. In order to achieve this, the arbitration service should have two components: one Responder for the API updates and one Publisher for notifying the conversion services. In addition to this, the conversion services should be updated to include a Subscriber. Let's see this in action.

Creating the arbitration service

A simple implementation of such a service would look like the following. First, we require cote and instantiate a responder for the API. Since we now have two responders, arbitration API and currency conversion responder, we need to introduce service segmentation by using key property. If we had no keys in our examples, some requests from our client.js would end up in currency conversion responder and we would get a correct response, but some other requests would end up in arbitration API, and since arbitration responder isn't listening to 'convert' events, the request would remain unanswered.

arbitration-service.js

const cote = require('cote');

const responder = new cote.Responder({ name: 'arbitration API', key: 'arbitration' });

Let's say we keep the rates in a local variable. This could just as well be a database call, but for the sake of simplicity let's keep this local.

const rates = {};

Now the responder shall respond to an rate updated request, allowing admins to update it from a back office application. The backoffice integration isn't important at this moment, but here is an example how back offices could interact with cote responders in the backend. Basically, this service should have a responder to take in the new rates for a currency exchange.

responder.on('update rate', (req, cb) => {
    rates[req.currencies] = req.rate; // { currencies: 'usd_eur', rate: 0.91 }

    cb(null, `changed ${req.currencies} rate to ${req.rate}`);
});

Creating a publisher

We now have the rates, but the rest of the system, namely, the conversion services aren't aware of this change yet. In order to update them of the changes, we should create a Publisher.

const publisher = new cote.Publisher({ name: 'arbitration publisher' });

Now whenever there's a new rate, we should utilize this Publisher. The update rate handler thus becomes:

responder.on('update rate', (req, cb) => {
    rates[req.currencies] = req.rate;

    cb(null, `changed ${req.currencies} rate to ${req.rate}`);

    publisher.publish('rate updated', req);
});
Click to see the complete arbitration-service.js file.

const cote = require('cote');

const responder = new cote.Responder({ name: 'arbitration API', key:'arbitration' });
const publisher = new cote.Publisher({ name: 'arbitration publisher' });

const rates = {};

responder.on('update rate', (req, cb) => {
    rates[req.currencies] = req.rate;

    cb(null, `changed ${req.currencies} rate to ${req.rate}`);

    publisher.publish('rate updated', req);
});

Since currently there are no subscribers in this system, nobody will be notified of these changes. In order to facilitate this update mechanism, we need to go back to our conversion-service.js and add a Subscriber to it.

Creating a subscriber

A Subscriber is a regular cote component, so we instantiate it with the following:

const subscriber = new cote.Subscriber({ name: 'arbitration subscriber' });

Put this line in conversion-service.js.

Subscriber also extends EventEmitter2, and although these services might run in machines that are continents apart, any published updates will end up in a Subscriber as an event for us to consume.

Here's how we might update conversion-service.js to listen to updates from the arbitration service.

subscriber.on('rate updated', (update) => {
    rates[update.currencies] = update.rate;
});

Let's not forget to change the use of requester and responder in our conversion service and in our client to use segmentation key.

conversion-service.js

const responder = new cote.Responder({ name: 'currency conversion responder', key: 'conversion' });

client.js

const requester = new cote.Requester({ name: 'currency conversion requester', key: 'conversion' });

That's it! From now on, this conversion service will synchronize with the arbitration service and receive its updates. The new conversion requests after an update will be done over the new rate.

Click to see the complete conversion-service.js file.

const cote = require('cote');

const responder = new cote.Responder({ name: 'currency conversion responder', key: 'conversion' });
const subscriber = new cote.Subscriber({ name: 'arbitration subscriber' });

const rates = { usd_eur: 0.91, eur_usd: 1.10 };

subscriber.on('rate updated', (update) => {
    rates[update.currencies] = update.rate;
});

responder.on('convert', (req, cb) => {
    const convertedRate = req.amount * rates[`${req.from}_${req.to}`];

    cb(null, `${req.amount} ${req.from} => ${convertedRate} ${req.to}`);
});

Click to see the complete client.js file.

const cote = require('cote');

const requester = new cote.Requester({ name: 'currency conversion requester', key:'conversion' });

const request = { type: 'convert', from: 'usd', to: 'eur', amount: 100 };

requester.send(request, (err, res) => {
    console.log(res);
});

Components Reference

cote hosts a number of components that together let you implement microservice communication. Below, you will find several examples on how to make use of each component.

By default, every component can discover and interact with every other component. This may not be desirable under certain conditions whereas security and network performance is of importance, so one can segregate or partition component clusters with keys and environments provided in configuration objects.

Also, all components support namespaces. Given as a property of the configuration object to the constructor, components adhere and act on namespaces if provided, and ignore other messages. Namespaces are also handy in that they let you wire a namespaced socket.io connection to the front-end. In other words, the namespaces here also serve as socket.io namespaces.

Requester

Requester queues requests until a Responder is available, and once so, it delivers the request. Requests will be dispatched to Responders in a round-robin way.

Example:

const cote = require('cote');

const randomRequester = new cote.Requester({
    name: 'Random Requester',
    // namespace: 'rnd',
    // key: 'a certain key',
    requests: ['randomRequest'],
});

setInterval(() => {
    const req = {
        type: 'randomRequest',
        val: Math.floor(Math.random() * 10),
    };

    randomRequester.send(req, (res) => {
        console.log('request', req, 'answer', res);
    });
}, 5000);

Requesters also support Promises, which gives you great flexibility when working with promise-based libraries or when you want to chain multiple Requesters and Responders.

Example with promises:

const cote = require('cote');
const randomRequester = new cote.Requester({ name: 'Random Requester' });

const makeRequest = (req) => randomRequester.send(req);

const req = {
    type: 'randomRequest',
    val: Math.floor(Math.random() * 10),
};

makeRequest(req)
    .then(console.log)
    .catch(console.log)
    .then(process.exit);

Example with async / await:

const cote = require('cote');
const randomRequester = new cote.Requester({ name: 'Random Requester' });

async function makeRequest () {
    const req = {
        type: 'randomRequest',
        val: Math.floor(Math.random() * 10),
    };

    const response = await randomRequester.send(req);
    console.log(response);

    process.exit();
}

makeRequest();

Timeout

A timeout could be configured for all Requesters as an environment variable COTE_REQUEST_TIMEOUT, or in advertisement options for specific Requester, or in a property called __timeout in first argument of requester.send method. Latter setting overrides former. Timeout is specified in milliseconds.

As environment variable for all requesters:

COTE_REQUEST_TIMEOUT=1000 node service.js

In advertisement settings:

new cote.Requester({ name: `Requester with timeout`, timeout: 1000 });

In send data:

requester.send({ type: 'find', __timeout: 2000 });

Responder

Responder is a component for responding to certain requests from a Requester. It's a descendant of EventEmitter2, and requests are regular events, therefore may be wildcarded or namespaced.

Responder may be used to add new modules to existing web servers / applications without ever changing the main server code. Only a Requester will be able to utilize a Responder.

You can use a Responder with a Sockend component to open a flexible API channel for the front-end. This greatly reduces time-to-market by providing a direct API for your front-end applications.

Example:

const cote = require('cote');

// Instantiate a new Responder component.
const randomResponder = new cote.Responder({
    name: 'Random Responder',
    // namespace: 'rnd',
    // key: 'a certain key',
    respondsTo: ['randomRequest'], // types of requests this responder
                                  // can respond to.
});

// request handlers are like any event handler.
randomResponder.on('randomRequest', (req, cb) => {
    const answer = Math.floor(Math.random() * 10);
    console.log('request', req.val, 'answering with', answer);

    cb(null, answer);
});

Responders also support Promises, , which gives you great flexibility when working with promise-based libraries or when you want to chain multiple Requesters and Responders.

Example with promises:

responder.js

const cote = require('cote');
const UserModel = require('UserModel'); // a promise-based model API such as
                                        // mongoose.

const userResponder = new cote.Responder({ name: 'User Responder' });

userResponder.on('find', (req) => UserModel.findOne(req.query));

requester.js

const cote = require('cote');
const userRequester = new cote.Requester({ name: 'User Requester' });

userRequester
    .send({ type: 'find', query: { username: 'foo' } })
    .then((user) => console.log(user))
    .then(process.exit);

Example with async / await

responder.js

const cote = require('cote');
const UserModel = require('UserModel'); // a promise-based model API such as
                                        // mongoose.

const userResponder = new cote.Responder({ name: 'User Responder' });

userResponder.on('find', (req) => UserModel.findOne(req.query));

requester.js

const cote = require('cote');
const userRequester = new cote.Requester({ name: 'User Requester' });

async function makeRequest() {
    const user = await userRequester.send({ type: 'find', query: { username: 'foo' });
    console.log(user);

    process.exit();
}

makeRequest();

Publisher

Publisher is a component for publishing certain events with arbitrary data. It may be used as a distributed EventEmitter. It may also be used in a scenario where some components need to be notified of updates, such as new tweets, etc. instead of polling for them. Only a Subscriber will get notifications from a Publisher.

The messages Publishers publish are volatile in that if there are no Subscribers listening, they are lost.

Publishers may be used in conjunction with a Sockend component, in which case the front-end clients will be notified of the events published. This is a very cool real-time communication mechanism for your apps with no proprietary technology like Meteor.

Example:

const cote = require('cote');

// Instantiate a new Publisher component.
const randomPublisher = new cote.Publisher({
    name: 'Random Publisher',
    // namespace: 'rnd',
    // key: 'a certain key',
    broadcasts: ['randomUpdate'],
});

// Wait for the publisher to find an open port and listen on it.
setInterval(function() {
    const val = {
        val: Math.floor(Math.random() * 1000),
    };

    console.log('emitting', val);

    // publish an event with arbitrary data at any time
    randomPublisher.publish('randomUpdate', val);
}, 3000);

Subscriber

Subscriber subscribes to events emitted from a Publisher.

Example:

const cote = require('cote');

const randomSubscriber = new cote.Subscriber({
    name: 'Random Subscriber',
    // namespace: 'rnd',
    // key: 'a certain key',
    subscribesTo: ['randomUpdate'],
});

randomSubscriber.on('randomUpdate', (req) => {
    console.log('notified of ', req);
});

Sockend

Sockend is the glue for carrying all the possibilities of cote to the next level with WebSockets over socket.io. Sockend makes Responders and Publishers available to the front-end and adhere to socket.io namespaces. It's the magic and the lost link for microservices. Without any configuration, you can expose APIs directly to the front-end.

Example:

index.html

<script src="/socket.io/socket.io.js"></script>
<script>
let socket = io.connect();
let socketNamespaced = io.connect('/rnd');

socket.on('randomUpdate', function(data) {
    console.log(data);
});

setInterval(function() {
    let req = {
        val: Math.floor(Math.random() * 10),
    };

    let req2 = {
        val: Math.floor(Math.random() * 10),
    };

    let req3 = {
        val: Math.floor(Math.random() * 10),
    };

    let req4 = {
        val: Math.floor(Math.random() * 10),
    };

    socket.emit('randomRequest', req, function(data) {
        console.log('normal', req.val, data);
    });

    socketNamespaced.emit('randomRequest', req2, function(data) {
        console.log('ns', req2.val, data);
    });

    socket.emit('promised request', req3, function(err, data) {
        console.log('normal promised', req3.val, err, data);
    });

    socketNamespaced.emit('promised request', req4, function(err, data) {
        console.log('ns promised', req4.val, err, data);
    });
}, 3000);
</script>

sockend.js

const cote = require('cote'),
    app = require('http').createServer(handler),
    io = require('socket.io').listen(app),
    fs = require('fs');

io.on('connection', (socket) => {
    socket.join('room1');
});

app.listen(process.argv[2] || 5555);

function handler(req, res) {
    fs.readFile(__dirname + '/index.html', (err, data) => {
        if (err) {
            res.writeHead(500);
            return res.end('Error loading index.html');
        }

        res.writeHead(200);
        res.end(data);
    });
};

const sockend = new cote.Sockend(io, {
    name: 'Sockend',
    // key: 'a certain key'
});

To connect responder and sockend, you need to add respondsTo: parameter to your options.

const randomResponder = new Responder({
    name: 'randomRep',
    respondsTo: ['randomRequest', 'promised request'], // types of requests this responder
    							  // can respond to.
});

To connect publisher and sockend, you need to add broadcasts: parameter to your options

let randomPublisher = new Publisher({
    name: 'randomPub',
    broadcasts: ['update1', 'update2'],
});

If your socket is connected to a namespace, you need to add the same namespace to the components that you want to expose. Even though socket.io are prefixed with /, with sockend you need to omit /.

socket.io-client

io.connect('/rnd'); // namespace in socket.io is declared as '/rnd'

sockend component

const randomResponder = new Responder({
    name: 'randomRep',
    namespace: 'rnd', // with sockend, we we omit the '/' and use just 'rnd'
    respondsTo: ['randomRequest', 'promised request'], // types of requests this responder
    							  // can respond to.
});

You can check complete code for Responders and Publishers in the examples folder. Fire them up on default or 'rnd' namespace and watch them glow with magic on http://localhost:5555. If you want to see more complete example of microservices with sockend integration, check out the e-commerce case study

Socket.io Rooms

Sockend supports socket.io rooms. All you need to do is add a __rooms or __room attribute to the published message.

const randomPublisher = new cote.Publisher({
    name: 'Random Publisher',
    // namespace: 'rnd',
    // key: 'a certain key',
    broadcasts: ['randomUpdate'],
});

randomPublisher.publish('randomUpdate', { val: 500, __rooms: ['room1', 'room2'] });
randomPublisher.publish('randomUpdate', { val: 500, __room: 'room1' });

Monitor

Monitor is the "top" of cote. It lists all the daemons it discovers regardless of namespace or key. Run examples/monitor.js and see all your active cote daemons.

Monitoring Tool

cote also has an infant of a monitoring tool that displays the cote ecosystem running in your environment in a nice graph. Run examples/monitoring-tool.js and navigate to http://localhost:5555 in your browser to see your cote network graph in action.

Advanced usage

While cote is extremely simple to get started, the requirements for a system running in production may demand further tweaking and advanced settings. Here are some of the advanced features of cote, which can be adjusted on several levels — as environment variables, as direct settings for the cote module when requiring it, or as direct settings for each component.

Until now, we only saw instantiating cote components with a single argument. In fact, all cote components have two constructor parameters. The first is used as the advertisement configuration which controls the data being advertised for auto-discovery. The second parameter is the discovery configuration and it controls the network-layer configuration and environments for components.

We'll see more details in the following section.

Environments

cote works over IP broadcast or multicast. This means, for example, in an office network where there are several developers running a project based on cote in their local machines, there might be chaos. Components on a developer's machine will discover other components on another developer's machine. This probably is not a desired effect, and fortunately cote offers a way to combat this.

By passing in an environment property to the configuration object of a component, one can control the scope of auto-discovery for that particular component. Components that are not in the same environment will ignore each other. This effectively creates network partitions.

environments can be set as an environment variable COTE_ENV.

Running a service with

COTE_ENV=developer-1 node service.js

sets all the components within that service to use developer-1 as an environment. This makes sure that however many modules service.js makes use of, they all will share the same environment, so this is the safest way to specify environments.

The other way to specify an environment is using the configuration argument to cote, given when requiring cote in the first place. Since Node.js modules are read and executed once from the disk, you need to make sure to pass in the configuration at least once, during the first require call. The subsequent requires to cote will return the same module, which already has your configuration. If you have a bootstrap in your application that runs as the first thing in the application, it might be a good idea to put this config there.

Example

const cote = require('cote')({ environment: 'developer-2' });

Now the components in these services won't discover and communicate with each other.

Another place this comes handy is multiple environments running on a single machine. Say you have a machine for your QA needs, where you host several environments for different tests, e.g. integration and qa. Again, components from different environments would mix up. Using a parametric environment, in this case, solves this problem.

Keys

cote has another mechanism to create partitions called keys. Since every component discovers and tries to communicate with every other component on the horizon (this is called a "mesh network"), if different services request and respond to different types of messages, you will experience lost messages. In other words, if service A responds to messages X and Y, and service B responds to messages Z and T, you will lose half of the messages, because messages Z and T will also end up at service A, but it won't know how to handle them. The same is true for service B: messages X and Y will end up at it, but service B won't know how to respond to them.

Keys are useful in this scenario: requesters and responders around service A and messages X and Y should use one particular key, and requesters and responders around service B and messages Z and T should use another key. In this case, no messages will be lost, and the services will be segregated.

In our experience, the best way to segregate services is to follow the principles of domain-driven design. In this regard, for example, each domain could have its own key. If you need more granular services, you should use multiple keys inside the same domain. The principle is to ensure distinct keys for a distinct set of messages. In other words, keys should represent a distinct set of requests.

Please refer to [Creating the arbitration service] (https://github.com/dashersw/cote#creating-the-arbitration-service) for an example of keys in action.

keys are given as parameters to the configuration objects.

When deciding whether to create a connection to another service, cote components make use of keys and environments together. Therefore, two components with exact same environments with different keys wouldn't be able to communicate.

Think of it as ${environment}_${key}.

Example

const cote = require('cote');

const purchaseRequester = new cote.Requester({
    name: 'Purchase Requester',
    key: 'purchase',
});

const inventoryRequester = new cote.Requester({
    name: 'Inventory Requester',
    key: 'inventory',
});

Unlike environments, keys can't be used as an environment variable or part of cote's configuration, but rather, should be provided as part of the first argument to a component.

Namespaces

cote includes a Sockend component that provides a direct channel to the frontend. This is extremely powerful and with power, comes great responsibility. Exposing all the Responders and Publishers in the backend to your frontend application probably isn't a good idea. Therefore cote offers namespaces, which map conveniently to socket.io namespaces.

To help increase the security of backend services, components with different namespaces won't recognize each other and try to communicate. This effectively segregates the front-facing components. In order to allow a component to talk to the frontend, you should use a namespace which shields that service from the rest of the system. By incorporating multiple components in a single service, you can basically create proxies and let your front-facing components interact with the rest of the system in a secure way.

Example

front-facing-service.js

const cote = require('cote');

const responder = new cote.Responder({
    name: 'Conversion Sockend Responder',
    namespace: 'conversion',
});

const conversionRequester = new cote.Requester({
    name: 'Conversion Requester',
    key: 'conversion backend',
});

responder.on('convert', (req, cb) => {
    conversionRequester.send(req.type, req, cb); // proxy the request
});

backend-service.js

const cote = require('cote');

const responder = new cote.Responder({
    name: 'Conversion Responder',
    key: 'conversion backend',
});

const rates = { usd_eur: 0.91, eur_usd: 1.10 };

responder.on('convert', (req, cb) => {
    cb(null, req.amount * rates[`${req.from}_${req.to}`]);
});

Just like keys, namespaces can also only be utilized as part of the first argument to a component.

Multicast address

cote works either with IP multicast or IP broadcast, defaulting to broadcast. If you wish to use multicast instead, you can pass in a multicast property with the configuration object to cote. This will make sure that the discovery will happen only with the given configuration.

In fact, this is the best way to segregate services, not in the application layer but at the network layer. This will create the minimal number of gossip messages and the biggest gains in terms of performance. Therefore, using different multicast addresses is better than using different environments or keys.

Much like environments, multicast addresses can be specified either as an environment variable or as part of the main configuration object to the cote require's. They can also be given as part of the second configuration object.

Example

As an environment variable:

COTE_MULTICAST_ADDRESS=239.1.11.111 node service.js

As part of cote's module configuration:

const cote = require('cote')({ multicast: '239.1.11.111' });

As part of each component's discovery configuration:

const cote = require('cote');

const req = new cote.Requester({ name: 'req' }, { multicast: '239.1.11.111' });

Broadcast address

While multicast is good for segmentation, certain scenarios may require the configuration be done over IP broadcast. In that case, broadcast address configuration helps. Much like multicast configuration, cote supports 3 different ways of supplying broadcast configuration.

Multicast configuration has precedence over broadcast. Therefore, when both configurations are applied, broadcast configuration will be ignored and multicast configuration will take over.

Also, cote uses broadcast by default. Hence, if no configuration is provided, the broadcast address will be set to 255.255.255.255. If you want to use broadcast, but have a different broadcast IP, you should configure it as shown below.

Example

As an environment variable:

COTE_BROADCAST_ADDRESS=255.255.255.255 node service.js

As part of cote's module configuration:

const cote = require('cote')({ broadcast: '255.255.255.255' });

As part of each component's discovery configuration:

const cote = require('cote');

const req = new cote.Requester({ name: 'req' }, { broadcast: '255.255.255.255' });

Controlling cote with environment variables

Here's a list of environment variables cote supports:

Variable name Description
COTE_ENV See Environments.
COTE_MULTICAST_ADDRESS See Multicast address.
COTE_BROADCAST_ADDRESS See Broadcast address.
COTE_USE_HOST_NAMES In certain, extremely rare conditions, auto-discovery might fail due to components reporting wrong IP addresses. If you find out that is the case, you can command cote to use the reported host names instead.
COTE_DISCOVERY_REDIS See Using centralized discovery tools.
COTE_DISCOVERY_REDIS_URL See Using centralized discovery tools.
COTE_DISCOVERY_REDIS_HOST See Using centralized discovery tools.
DISCOVERY_HOSTNAME See Using centralized discovery tools.
COTE_REQUEST_TIMEOUT See Requester Timeout.
COTE_LOG Boolean. Whether to display hello and status logs for other discovered services. Has precedence over COTE_STATUS_LOGS_ENABLED and COTE_HELLO_LOGS_ENABLED.
COTE_HELLO_LOGS_ENABLED Boolean. Whether to display hello logs from other discovered services.
COTE_STATUS_LOGS_ENABLED Boolean. Whether to display status logs from other discovered services. Has precedence over COTE_HELLO_LOGS_ENABLED.
COTE_LOG_UNKNOWN_EVENTS Boolean. Whether to log a message when a responder or subscriber receives an event that it has no listeners for. Defaults to true.
COTE_CHECK_INTERVAL Integer. The interval for checking if a discovered service has sent a heartbeat since the last check.
COTE_HELLO_INTERVAL Integer. The interval for sending a heartbeat hello signal. Should be less than COTE_CHECK_INTERVAL.
COTE_NODE_TIMEOUT Integer. The timeout duration that determines if a service is unreachable and thus removed. Should be greater than COTE_CHECK_INTERVAL.
COTE_IGNORE_PROCESS Boolean. Whether the services defined in this process should ignore other services from this process. This might be useful in a high-availability setup where one wants to enforce collaboration of services over the network, instead of local services within each process.

Deploying with Docker Cloud (deprecated)

cote plays extremely well with Docker Cloud. Even if your cloud provider doesn't support IP broadcast or multicast, you can still have the same functionality with Docker Cloud's Weave overlay networks.

Just deploy your cote applications just like any other Node.js application and even when your containers run in different machines on different continents, as long as they share an overlay network — which Docker Cloud assigns by default anyway — everything will work as expected.

Make sure to check out the e-commerce case study that implements a complete e-commerce application with microservices using cote. It features example Dockerfiles and docker-compose configurations in addition to Docker Cloud configurations.

It also has a Docker Swarm configuration to get you started on using cote with Docker Swarm, in any cloud environment.

Using centralized discovery tools

cote is built to be zero-configuration, and relies on IP broadcast/multicast to work. cloud providers don't support this functionality (and they won't) out of the box. In these cases, one can use the Weave network overlay integration. However, this may not be suitable for everyone, due to varying reasons.

Welcome redis

In these cases, in order to let cote work, we developed a plugin mechanism to accommodate different solutions that can serve as the automated service discovery tool. Currently, redis is supported out of the box, and cote makes use of the node_redis library, in case you want to use redis as the central discovery tool. If you need to use anything other than redis, please open a new issue and we may be able to help.

You should also set DISCOVERY_HOSTNAME to the IP address of the container/instance since it defaults to machine's hostname which in most cloud/docker setups is not routable.

Configuring redis

cote aims to be as zero-conf as possible. Therefore, the discovery backend should be invisible to the developer. Since IP broadcast/multicast functionality is environment-specific, it makes sense to configure a centralized solution via environment variables as well. This way, the container deployment configurations such as Docker Swarm stack definitions can make use of the additional redis backend functionality, while developers can still use IP broadcast/multicast locally, with the same source code.

That's why cote uses environment variables that start with COTE_DISCOVERY_REDIS. cote transforms any environment variable that starts with COTE_DISCOVERY_REDIS to proper configuration for the node_redis library. For example, COTE_DISCOVERY_REDIS_URL=redis becomes { url: 'redis' } and COTE_DISCOVERY_REDIS_HOST=redis COTE_DISCOVERY_REDIS_PORT=6379 becomes { host: 'redis', port: '6379' }.

Variable name Description
COTE_DISCOVERY_REDIS If you are running redis on localhost, setting this variable to true will use the locally available redis at port 6379. If you need any other redis URL or host, you don't need to use this variable.
COTE_DISCOVERY_REDIS_URL Sets the redis connection URL. Has to start with either redis:// or //. Enables the redis plugin.
COTE_DISCOVERY_REDIS_HOST Sets the redis connection host name. Enables the redis plugin.
COTE_DISCOVERY_REDIS_PORT Sets the redis connection port. Enables the redis plugin.
DISCOVERY_HOSTNAME This defaults to your machine's hostname. If this is not routable you need to set this to the routable IP address of this instance.

cote also supports other connection options supported by node_redis in the same manner.

Example

As an environment variable:

COTE_DISCOVERY_REDIS_HOST=redis DISCOVERY_HOSTNAME=127.0.0.1 node service.js

As part of cote's module configuration:

const cote = require('cote')({ redis: { host: 'redis' } });

As part of each component's discovery configuration:

const cote = require('cote');

const req = new cote.Requester({ name: 'req' }, { redis: { host: 'redis' } });

FAQ

Is cote production-ready?

cote is battle-tested, solid and has been running in production across thousands of services since its inception in 2013. cote follows Semantic Versioning and is production-ready.

Usage with PM2

Make sure you don't run any of your services in cluster mode. It messes up the service discovery since it tries to load balance the UDP ports used internally for this purpose.

To use Cote properly within PM2 cluster mode, server instances should only be instantiated once. To do so, utilize process.env.pm_id, which will return a value between 0 and the total number of instances(N). For example, if 10 instances of your app are running in cluster mode pm2 start app.js -i 10, process.env.pm_id will return a value of (0-9) inclusively.

// In thise case, we choose only the third app instance (2 because it is zero based) to instantiate a "SERVER"
// any number from 0 through 9 can be used, instead of 2
if (process.env.pm_id == 2) {
   const cote = require('cote');
   const timeService = new cote.Responder({
      name: 'Time Service'
   });
   timeService.on('time', (req, cb) => {
     cb(new Date());
   });
}

Running with cloud providers (AWS, DigitalOcean, etc)

Most cloud providers block IP broadcast and multicast, therefore you can't run cote in a multi-host environment without special software for an overlay network. For this purpose, Docker is the best tool. Deploy your application in Docker containers and you can take advantage of its overlay networks. Users of Docker Swarm can make use of the Weave Net plugin. Weave also has an addon for enabling multicast/broadcast for Kubernetes.

If you find the solutions with Docker Swarm and Kubernetes to be hard to get started with, you can use redis as a centralized discovery tool. Check out Using centralized discovery tools to see how you can set up redis to work with cote.

Contribution

cote is under constant development, and has several important issues still open. We would therefore heavily appreciate if you headed to the project to see where we are in the development, picked an issue of your taste and gave us a hand.

If you would like to see a feature implemented or want to contribute a new feature, you are welcome to open an issue to discuss it and we will be more than happy to help.

If you choose to make a contribution, please fork this repository, work on a feature and submit a pull request. cote is the next level of microservices — be part of the revolution.

MIT License

Copyright (c) 2013 Armagan Amcalar

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

FOSSA Status

cote's People

Contributors

dashersw avatar dmitry-ilin avatar drubin avatar frontconnect avatar gnought avatar jessety avatar karimmakhloufi avatar kenjones-cisco avatar knoxcard avatar matejthetree avatar mehmettamturk avatar mertdogar avatar orrgal1 avatar otothea avatar pelzerim avatar roblabat avatar robophil avatar scottrudiger avatar tk120404 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cote's Issues

Rejecting promises

I am getting empty error messages from the Requester:

responder

authService.on('getAdminToken', req => {
  console.log('getAdminToken');
  const adminToken = {
    role: 'admin'
  };
  const options = {
    audience: 'http://domain.com/audience',
    expiresIn: '6 month'
  }
  return new Promise((resolve, reject) => {
    const jwt = sign(adminToken, config.jwtSecret, options);
    if(!jwt) reject(new Error('failed to sign token'));
    resolve(jwt);
  });
});

requester

requester.send({ type: 'getAdminToken' })
  .then((token) => {
    winston.info(token);
  })
  .catch(e => winston.error(e));

output

{
  "level": "error",
  "message": ""
}

I can't see the problem. What did I get wrong with cote?

It also sends the empty message when I do:


authService.on('getAdminToken', req => {
  return new Promise((resolve, reject) => {
    reject(new Error('test error message'));
  });
});

Issue Installing Cote via NPM

There's an issue involving the use of 'dashersw/axon' as it should be just 'axon'. If that can be fixed back that would be helpful as the old naming scheme isn't in npm.


npm ERR! 404 'dashersw/axon' is not in the npm registry.

Communicate with python service

Hi,
Thanks for this library!!. This library really simplifies many things.
I was wondering if this has a python lib. I have some node-js apps and some python apps. If there is no python lib available, what is the best way to communicate between these two apps? Redis? NATS? GRPC? PUB-SUB?

Simple environment test

This seems pretty interesting but it also looks like in order to give it a spin will take a no trivial effort.
It would be nice to see a script that pull up a few services.

Maybe you can use a docker-compose file that can spin up a few docker containers with the services running inside.

It would be also nice to see the output of the monitoring tool.

readme.md typo

In Readme.md,

const responder = new cote.Requester({ name: 'currency conversion responder' });

should be changed to

const responder = new cote.Responder({ name: 'currency conversion responder' });

Let components use hostnames when establishing connection

Cote components connect to each other via IP addresses in the advertisement packages, but under certain circumstances (Docker Cloud, for example) IP addresses may be misleading since the advertisement packets are routed through different routers.

One solution to the problem is letting the daemons use the advertised hostnames. If proper DNS configuration is made, hostnames can enable communication in a multi router setup.

Implement preliminary internal queue for requesters

Requesters have to wait for the ready event before they can send a request. Otherwise the requests will just be lost.

Implement an internal queue for the requesters so that messages will be saved until the ready event occurs and sent automatically when the ready event is received.

Problem linking more then 2 services

I'm new to cote and maybe I'm missing something but I can't make more then 2 services to work.

Here is an isolated example:

index.js

var cote = require('cote');
var SERVERS = [
    'server0',
    'server1',
    'server2'
];
var client = new cote.Requester({
    name: 'index.js',
    requests: SERVERS
});

function nextRequest(i) {
    i = (i % SERVERS.length);

    console.log('Request to server' + i);
    client.send({type: 'server' + i}, function (response) {
        console.log('response', response);
    });

    setTimeout(function () {
        nextRequest(i + 1);
    }, 1000);
}

client.on('ready', function () {
    nextRequest(0);
});

server0.js

var cote = require('cote');
var server0 = new cote.Responder({name: 'server0', respondsTo: ['server0']});

server0.on('server0', function (req, cb) {
    cb('server0 response ' + new Date());
});

server1.js

var cote = require('cote');
var server1 = new cote.Responder({name: 'server1', respondsTo: ['server1']});

server1.on('server1', function (req, cb) {
    cb('server1 response ' + new Date());
});

server2.js

var cote = require('cote');
var server2 = new cote.Responder({name: 'server2', respondsTo: ['server2']});

server2.on('server2', function (req, cb) {
    cb('server2 response ' + new Date());
});

To run it do:

npm install cote
node index.js &
node server0.js &
node server1.js &
node server2.js &

Or just run in different terminal windows.

When I change SERVERS list in index.js file to just one server (server0) for example and run only node index.js and node server0.js then I got response for every request. Whenever I'm enabling more servers I'm starting to loose responses.

I'm using [email protected] and [email protected]
Am I missing something?

Allow disabling monitor screen

We should be able to use the monitor for silent operation. Adding disableScreen would improve the usefulness of this component a lot.

Architecture advice

Hi there!

I currently works on a CRM project (side project)
There is 1 front app (Vue.js), which calls 1 HTTP API (Express). This API calls Services (cote.js) and render results to the front app.

Is it better to have an API between front app and Services, or use Sockend possibilities (remove the API and call Services directly from front app)? API is usefull for authentication, but add a HTTP call layer

Implement Channel component

cote components are great for one-way communication (pub/sub, or req/res).

In practice, some microservices need a two-way communication channel. Since cote components work as TCP servers and clients, currently we have to implement this two-way communication via two different components, which means we would have two TCP servers for this. This is a waste.

Not working on CentOS 7

Hi,

I've just created two new services based on the sample code on the website and it just doesn't work. I've tried running as separate processes on the same machine and on two separate VM's on the same network. I've opened the ports on iptables and turned off the firewall, but still no joy.

The fact that it doesn't even work on the same machine tells me something is up.

It works fine on a single instance of unbuntu. What is different with CentOS?? Any ideas?

Enable plugin support for service discovery

Until Kubernetes and Docker Swarm get native support for IP broadcast/multicast, it makes sense to enable service discovery over another tool.

This is fundamentally at odds with cote's zero configuration approach; but in order to enable cote on limited environment such as public clouds, it's viable to introduce an optional alternative. This will also lead to greater adoption of cote.

CoteJs Protocol & Integration with Express

@dashersw This is somewhat a two part question:

  1. The documentation lists out which protocols are not used but does not really state what protocol is used in cotejs. It sort of implies that it is a custom light weight protocol. Is that true? If that is the case, how do I go about testing my work independently on a request/response level without necessarily having to setup another service to test my work?

  2. I have seen the express JS library example from a request stand point. That example makes a good API gateway example. But how would you incorporate the Responder to expressJs?

Missing requests sending by cote.Requester

I have Requester that sends many requests (for example over some interval).
I want to make Responder that initiliaze then answer one request and die. I did it in several ways, but always when i start next Responder i miss some of requests.

What proper way to do that?

Implement middleware for responders / subscribers

Certain functions such as auth or request decoration would make use of middlewares, as in, virtually everywhere (Express & co).

The following code...

responder.on('request', function(req, cb) {
    // do something;
});

should still work with a middleware:

responder.on('request', middleware, function(req, cb) {
    // do something if middleware allows
});

Discussion: Failure handling and Circuit Breaker-ish behavior

Hi Armagan, this is not an issue, just an invitation to discuss your vision on the subject.

First of all, I want to thank your for a great project, looks very sleak, I am definitely giving it a try.

As I brushed through your repo, I noticed that you have patched the Axon library to queue up (indefinitely) the outstanding requests until the connection is up. Although viable for some use-cases, this decision looks very opinionated, hence my friendly inquiry.

Do you have any plans for advancing the mechanism with configurable failover behavior? Think response timeouts, limiting the queue length, defaults for failed requests, or may be even some state machinery like in circuit breaker.

Could you share your general opinion on this topic?

Thank you!

Status Logging

I have a pretty simple Requester/Responder setup running on a single dev machine at the moment. There is a set of main app instances with a requester and a set of workers with a responder.

  1. Is it expected behavior for the Requesters/Responders to go online/offline every few seconds?
  2. If you set statusLogsEnabled: false for the options it only disables logging for offline status; online status is still logged (repeatedly).

Service discovery in Redis?

Wouldn't it be easier for the community to adopt cote.js(since it needs environments with multicast or broadcast enabled) if the service discovery was made through something like Redis? I know you have this philosophy of zero-dependency, but since you are already advising people to setup docker environments to get a overlay network, they could spin a simple redis instance to handle this stuff

Responder and Requester Problems

I created 2 services on different projects and another project to call this:

const cote = require('cote')
//responder 1
const responder1 = new cote.Responder({
   name: 'resp1'
});
//responder 2
const responder2 = new cote.Responder({
   name: 'resp2'
});
//responder1 -> mtd1
responder1.on('mtd1', (req, cb) => {
   cb('OK1!');
});
//responder2 -> mtd2
responder2.on('mtd2', (req, cb) => {
   cb('OK2!');
});

//creating requester
const requester = new cote.Requester({
   name: 'req1'
});

const request = {
   type: 'mtd1',
   val: 'test'
};


setInterval(() => {
   console.log('calling...')
   requester.send(request, (res) => {
       console.log(res);
   });
}, 1000);

When I call mtd1, only half of the answers come back.
If I delete responder2 all responses come back.
If I create responder3, only 33% of requests come back.

What am I doing wrong? Thank you.

Explanation on doc.

Hi @dashersw,

I just need more light be shed on this part of the documentation

Note: By default, every Requester will connect to every Responder it
discovers, regardless of the request type. This means, every Responder should
respond to the exact same set of requests, because Requesters will
load-balance requests between all connected Responders regardless of
their capabilities, i.e, whether or not they can handle a given request.

If you have multiple Responders with varying response handlers, you will
experience lost requests. In cote, this separation between responsibilities is
called segmentation, or partitioning. If you wish to segment your requests in
groups, you can use keys. Check out keys for a detailed guide on how
and when to use segmentation.

Also, could you explain more on key, namespace, and environment? When to use each.
Seems to me that key and environment are interchangeable

Add a GUI for Monitor component

Currently the Monitor component works in CLI, which is not the best place to inspect tens of other components. There should be a Sockend component in Monitor to push the network information to a management frontend. The frontend should draw the mesh network in a dynamic fashion, such as with D3.

Implement conditional publishers

Publishers are currently blind. We could have a component that allows subscribers to selectively subscribe (and unsubscribe) to messages from publishers. That would increase network efficiency, since publishers would ignore publish commands if no listeners matched.

Cote.js (promise) in Express

Hi!
I'm trying to use cote.js in my express app.
Here my route code:

...
router.get('uuid', (req, res, next) => mainSvc.getUuid()
        .then( data => res.json(data) )
        .catch( next )
);

Here mainSvc code:

const      cote = require('cote'),
  uuidRequester = new cote.Requester({ name: 'uuid requester' });

module.exports = {
...
  getUuid:  ()=> {
       const  request = { type: 'uuid' };
        
       return  uuidRequester.send( request )
            .then( data  => {
                console.log('Data:', data);
                return  data;
            })
            .catch( err => {
                console.error('Error:',  err);
                return  Promise.reject( err );
            })
       ;
  }
...
};

Here my service to generate uuid:

const   uuid = require('uuid/v4'),
        cote = require('cote'),
   responder = new cote.Responder({ name: 'uuid responder' });

responder.on('uuid', (res, cb) => {
    cb({ uuid: uuid() });
});

And in console I got error:

uuid requester > service.online uuid responder#47cdd6ce-ccfb-4043-bcf7-e01b54b1b626 on 8000
Error: { uuid: 'e917380b-8128-4bd7-b087-3ed136987289' }

Error

What I did wrong and why I got Error instead Data?
Thanks

Cote Just Doesn't Work

I try cote for the first time. I already run the time-service.js and client.js also with the monitoring tool. But nothing happened. Do i miss something?

Express API plus CoteJS standalone services sample

Hello,

Is possible to create Express Docker Containers, exposing an API Restful that responds to
CoteJS services, but those services are in anothers Docker containers?

If I use locally, run express at 3000 port, and them run cotejs services, it works.
Expresss routes send to cotejs that responds automatically.

But and I tried use those projects using Docker as I said above, the Express router doesnt "find"
the CoteJS that are running in another Docker container.

Can you explain me for how can I make it work?

Thanks ;)

Transfer big files (10MB)

Hello.
How can I transfer ZIP file (10MB) from Requester to Responder? Is Cote allows to do this?
Thanks.

Can this actually be used in Kubernetes?

I am trying to implement cote in kubernetes on IBM bluemix and am having alot of trouble getting it to work.

The bluemix kubernetes implementation has Calico plugin at network layer and Pods can definitely access each other via IP.

I have put a redis service in place and set cote to use that for discovery which works:

2017-09-13T05:23:41.920741203Z core space requester > service.online idea service space responder#7528508b-a087-414e-ade8-2a95825472e1 on 8002

however once it starts actually messaging it seems to drop back to hostnames that do not resolve inside kubernetes, e.g.

2017-09-13T05:23:46.925401243Z Error: getaddrinfo EAI_AGAIN idea:8000
2017-09-13T05:23:46.925441235Z     at Object._errnoException (util.js:1041:11)
2017-09-13T05:23:46.925447197Z     at errnoException (dns.js:58:15)
2017-09-13T05:23:46.925452669Z     at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:95:26)

I've trawled through the implementation of node-discover and can see the hostname getting encoded in the message but I'm unclear if that hostname is used to respond.

Is there a way to insist on using IP addresses for communication rather than hostnames or am I looking down the wrong path here?

Multi-language Microservice Environments

Are there any plans to draw up a spec for the various communication elements (particularly service discovery)? To perhaps allow implementations in other languages? A key aspect of micro-services for me (and I expect many others) is the ability to use different languages for different services (as appropriate). I appreciate this library is primarily aimed at Node.js but if there was at least a spec it would open up the possibility of using it in a multi-language environment.

Implement key prefixes as global config

Sometimes multicast, broadcast and key configs are not feasible enough to specify different environments in a connected network. Then there's a deliberate need for an environment setting which should be set at require-level; so that no components need to know about (and set) this setting.

Authentication consideration

Hello,

I want to use JWT between microservices (so between Requester <=> Responder, and Publisher <=> Subscriber). It is possible? Or something else to authenticate call?

Awesome project by the way: easy to understand and to use :)

Tests

Does it have tests?

Persistence?

Requesters queue messages until a responder is online...but how is this done?

Document how sockend works in more detail

The readme features basic documentation for Sockends, and doesn't give a complete example for how and why it works. Therefore we need more detailed documentation in the readme. Certain things like simple security mechanisms via respondsTo and broadcasts configurations should also be taken into account.

Pre-built events

Hi. I want to ask about events 'added' and 'removed'
When I add this snipped of code to responder:
responder.on( (req, info) => console.log(req, info) );
in console I see such outcome (when some component is found):

cote:added { isMaster: false,
  isMasterEligible: true,
  weight: -0.1512131192202,
  address: '192.168.56.1',
  advertisement:
   { name: 'REQUESTER',
     key: '$$',
     axon_type: 'req',
     type: 'service' },
  id: 'c63f7216-06ba-472a-9a37-6dcf3547b4bf',
  processId: '7e12d231-3cb3-4d38-8b62-eda680cfbfa3',
  processCommand: 'D:\\Projects\\cote-study\\ms\\requester',
  lastSeen: 1512131202220,
  hostName: 'DESKTOP-CC122GO',
  port: 12345 }

Can anyone explain me what is it about and how can I utilize it?
Also I want to know when callback in 'on' method accepts more than 1 argument?
Thanks

Using Redis to discovery tool problems

Hello I'm using cote with Redis to discovery.
When I use in the same machine, all works ok, but when I use 2 or more machines nothing works.
Looking pub messages inside redis I see the IP address field 127.0.0.1, I thought that should be the network address 10.14.0.44 or 10.14.1.163 at this case. What's wrong?

responder1 ip 10.14.0.44
1511981478.601845 [0 10.14.0.44:38922] "publish" "cote" "{\"event\":\"hello\",\"pid\":\"dc136bc2-c818-4e00-a215-044348875b2d\",\"iid\":\"22d137fd-4ee9-437d-9920-d74f10078da9\",\"hostName\":\"HPDEV\",\"data\":{\"isMaster\":false,\"isMasterEligible\":true,\"weight\":-0.1511976109907,\"address\":\"127.0.0.1\",\"advertisement\":{\"name\":\"Estabelecimentos:Responder\",\"key\":\"$$Estabelecimentos\",\"axon_type\":\"rep\",\"port\":8005,\"type\":\"service\"},\"id\":\"22d137fd-4ee9-437d-9920-d74f10078da9\",\"processId\":\"dc136bc2-c818-4e00-a215-044348875b2d\",\"processCommand\":\"projects/mpre_estabs\"}}"

responder2 ip 10.14.1.163
1511982019.531203 [0 10.14.1.163:37152] "publish" "cote" "{\"event\":\"hello\",\"pid\":\"1a8d3288-c8aa-4218-b683-eb36f7c1b562\",\"iid\":\"9ff0f993-d8c7-4bc1-a272-86f04b05a658\",\"hostName\":\"ecelepar16853\",\"data\":{\"isMaster\":false,\"isMasterEligible\":true,\"weight\":-0.1511981997474,\"address\":\"127.0.0.1\",\"advertisement\":{\"name\":\"Estabelecimentos:Responder\",\"key\":\"$$Estabelecimentos\",\"axon_type\":\"rep\",\"port\":8002,\"type\":\"service\"},\"id\":\"9ff0f993-d8c7-4bc1-a272-86f04b05a658\",\"processId\":\"1a8d3288-c8aa-4218-b683-eb36f7c1b562\",\"processCommand\":\"menorpreco_estabelec/index.js\"}}

Cannot both subscribe and publish in the same process

I know it can sounds weird (it's about multiple processes and microservice I know! :) ), but I'm trying to subscribe and publish in the same process but that does not work with this example:

var Subscriber = require('cote').Subscriber;

var randomSubscriber = new Subscriber({
  name: 'randomSub',
  // namespace: 'rnd',                                                                                                                                                                                                                                                  
  subscribesTo: ['randomUpdate']
});

randomSubscriber.on('randomUpdate', function(req) {
  console.log('notified of ', req);
});

var Publisher = require('cote').Publisher;

// Instantiate a new Publisher component.                                                                                                                                                                                                                               
var randomPublisher = new Publisher({
  name: 'randomPub',
  // namespace: 'rnd',                                                                                                                                                                                                                                                  
  broadcasts: ['randomUpdate']
});

// Wait for the publisher to find an open port and listen on it.                                                                                                                                                                                                        
randomPublisher.on('ready', function() {
  setInterval(function() {
    var val = {
      val: ~~(Math.random() * 1000)
    };

    console.log('emitting', val);

    // publish an event with arbitrary data at any time                                                                                                                                                                                                                 
    randomPublisher.publish('randomUpdate', val);
  }, 3000);
});

Is there something I need to change?

Multiple calls with same Requester

Hi, I'm instantiating a cote Requester and I try to use it multiple times (pointing to the same Responder in another process), but only the first Request is getting a response, the following ones don't. What could be happening ? Thanks!

ES6 rewrite

We are rewriting cote with a modern, ES6-based approach. It's currently on the es6 branch.

Implement sticky sessions for requesters

Currently we have a few load-balancing scenarios. Above all these, to let developers create stateful services, cote requesters should support sticky sessions, in that, it should be configurable that certain requests could be directed to certain responders.

Let cote take default discovery options

It's somewhat inconvenient to set the same discovery options, such as the multicast/broadcast address, for more than one component. Cote should provide a way to set such default settings.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.