Skip to content

Commit

Permalink
Merge pull request #13 from ph0bos/round-robin-instance-selection
Browse files Browse the repository at this point in the history
Allow for multiple service provider strategies
  • Loading branch information
ph0bos committed Mar 8, 2016
2 parents fad505e + f66b788 commit 8c7b614
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 15 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,10 @@ var serviceDiscovery = ServiceDiscoveryBuilder
.basePath('services')
.build();

// Service Provider
// Service Provider (providerStrategy: 'RoundRobin' or 'Random')
var serviceProvider = serviceDiscovery.serviceProviderBuilder()
.serviceName('my/service/name/v1')
.providerStrategy('RoundRobin')
.build();

// Discover available Services and provide an instance
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ var Zoologist = require('./lib/Zoologist');
var ServiceDiscoveryBuilder = require('./lib/ServiceDiscoveryBuilder');
var ServiceInstanceBuilder = require('./lib/ServiceInstanceBuilder');
var ServiceProvider = require('./lib/ServiceProvider');
var ServiceProviderBuilder = require('./lib/ServiceProviderBuilder');
var GetChildrenBuilder = require('./lib/GetChildrenBuilder');

exports.Zoologist = Zoologist;
exports.ServiceDiscoveryBuilder = ServiceDiscoveryBuilder;
exports.ServiceInstanceBuilder = ServiceInstanceBuilder;
exports.ServiceProvider = ServiceProvider;
exports.ServiceProviderBuilder = ServiceProviderBuilder;
exports.GetChildrenBuilder = GetChildrenBuilder;
2 changes: 1 addition & 1 deletion lib/ServiceDiscovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ ServiceDiscovery.prototype.getData = function () {
* @method serviceProviderBuilder
*/
ServiceDiscovery.prototype.serviceProviderBuilder = function () {
var builder = new ServiceProviderBuilder(this);
var builder = ServiceProviderBuilder.builder();

builder.serviceDiscovery(this);

Expand Down
49 changes: 40 additions & 9 deletions lib/ServiceProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ util.inherits(ServiceProvider, EventEmitter);
*
* @param zooKeeper {ZooKeeperClient} A ZooKeeperClient instance.
* @param serviceName {String} Service name.
* @param: providerStrategy {String} Instance selection algorithm ('Random' or 'RoundRobin').
*/
function ServiceProvider(serviceDiscovery, serviceName) {
this.serviceDiscovery = serviceDiscovery;
this.serviceName = serviceName;
function ServiceProvider(serviceDiscovery, serviceName, providerStrategy) {
this.serviceDiscovery = serviceDiscovery;
this.serviceName = serviceName;
this.providerStrategy = providerStrategy || 'RoundRobin';
this.lastInstanceIndex = 0;
};

/**
Expand Down Expand Up @@ -76,9 +79,17 @@ ServiceProvider.prototype.getInstance = function(callback) {
var self = this;
var absPath = [ self.serviceDiscovery.basePath, self.serviceName ].join('/');

var providerStrategyFunction;

if (self.providerStrategy === 'Random') {
providerStrategyFunction = selectServiceByRandomiser;
} else {
providerStrategyFunction = selectServiceByRoundRobin;
}

async.waterfall([
getAvailableServices,
selectRandomService,
providerStrategyFunction,
getServiceData
], completed);

Expand All @@ -87,10 +98,10 @@ ServiceProvider.prototype.getInstance = function(callback) {
self.serviceDiscovery.client.getClientwithConnectionCheck().getChildren(absPath, null, function(err, serviceList, stat) {
cb(err, serviceList, stat);
});
}
};

// Select a random service instance
function selectRandomService (serviceList, stat, cb) {
function selectServiceByRandomiser (serviceList, stat, cb) {
if (serviceList.length === 0) {
return callback(new NotFoundError('No services located for ID: ' + self.serviceName));
}
Expand All @@ -99,12 +110,32 @@ ServiceProvider.prototype.getInstance = function(callback) {
var servicePath = [ absPath, serviceId ].join('/');

cb(null, servicePath);
}
};

// Select the next service using a robin robin approach
function selectServiceByRoundRobin (serviceList, stat, cb) {
if (serviceList.length === 0) {
return callback(new NotFoundError('No services located for ID: ' + self.serviceName));
}

var serviceId;

if (serviceList[++self.lastInstanceIndex]) {
serviceId = serviceList[self.lastInstanceIndex];
} else {
self.lastInstanceIndex = 0;
serviceId = serviceList[0];
}

var servicePath = [ absPath, serviceId ].join('/');

cb(null, servicePath);
};

// Return the data for a service
function getServiceData (servicePath, cb) {
self.serviceDiscovery.client.getClient().getData(servicePath, null, cb);
}
};

// All done, call master callback
function completed (err, data, stat) {
Expand All @@ -117,7 +148,7 @@ ServiceProvider.prototype.getInstance = function(callback) {
data.serviceUrl = data.uriSpec + '/' + data.name;

return callback(err, data, stat);
}
};
};

module.exports = ServiceProvider;
17 changes: 15 additions & 2 deletions lib/ServiceProviderBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@

var ServiceProvider = require('./ServiceProvider');

var DEFAULT_PROVIDER_STRATEGY = 'RoundRobin';

function ServiceProviderBuilder() {
this.serviceProvider = {};
this.strategy = DEFAULT_PROVIDER_STRATEGY;
};

function builder() {
return new ServiceProviderBuilder();
};

ServiceProviderBuilder.prototype.serviceDiscovery = function(serviceDiscovery) {
Expand All @@ -22,10 +29,16 @@ ServiceProviderBuilder.prototype.serviceName = function(serviceName) {
return this;
};

ServiceProviderBuilder.prototype.providerStrategy = function(strategy) {
this.strategy = strategy;
return this;
};

ServiceProviderBuilder.prototype.build = function() {
return new ServiceProvider(
this.serviceProvider.serviceDiscovery,
this.serviceProvider.serviceName);
this.serviceProvider.serviceName,
this.strategy);
};

module.exports = ServiceProviderBuilder;
module.exports.builder = builder;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zoologist",
"version": "0.4.11",
"version": "0.4.13",
"description": "A Curator-esque framework for ZooKeeper",
"main": "index.js",
"keywords": [
Expand Down
10 changes: 10 additions & 0 deletions test/ServiceDiscovery.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ describe('ServiceDiscovery', function() {
done();
});

it('should create a ServiceProvider with a pre-populated ServiceDiscovery property that is capable of having an overriden providerStrategy when calling the result of serviceProviderBuilder()', function(done) {
var serviceProviderBuilder = serviceDiscovery.serviceProviderBuilder();

serviceProviderBuilder.providerStrategy('RoundRobin');

serviceProvider = serviceProviderBuilder.build();
serviceProvider.serviceDiscovery.should.be.a('object');
done();
});

it('should find service instances when calling queryForInstances() with a valid serviceId', function(done) {
serviceDiscovery.registerService(function (err, data) {
data.address.should.be.a('string');
Expand Down
2 changes: 1 addition & 1 deletion test/ServiceProvider.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('ServiceProvider', function() {
.basePath('services')
.build();

serviceProvider = new ServiceProvider(serviceDiscovery, 'my/service/v1');
serviceProvider = new ServiceProvider(serviceDiscovery, 'my/service/v1', 'RoundRobin');

serviceDiscovery.registerService(function onRegister(err, data) {
done();
Expand Down
49 changes: 49 additions & 0 deletions test/ServiceProviderBuilder.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
var mocha = require('mocha');
var mockery = require('mockery');
var should = require('chai').should();

var Zoologist = require('..').Zoologist;
var ServiceInstanceBuilder = require('..').ServiceInstanceBuilder;
var ServiceDiscoveryBuilder = require('..').ServiceDiscoveryBuilder;
var ServiceProviderBuilder = require('..').ServiceProviderBuilder;

describe('ServiceDiscovery', function() {

beforeEach(function(done){
client = Zoologist.newClient('127.0.0.1:2181');
client.start();

client.once('connected', function () {
builder = ServiceDiscoveryBuilder.builder();

serviceInstance =
ServiceInstanceBuilder
.builder()
.address('localhost')
.port(12345)
.name('my/service/v1')
.build();

serviceDiscovery =
builder
.client(client)
.thisInstance(serviceInstance)
.basePath('services')
.build();

serviceProvider =
ServiceProviderBuilder.builder()
.serviceDiscovery(serviceDiscovery)
.providerStrategy('RoundRobin')
.build();

serviceDiscovery.registerService(function onRegister(err, data) {
done();
});
});
});

it('should create a service provider builder instance', function() {
serviceProvider.should.be.a('object');
});
});

0 comments on commit 8c7b614

Please sign in to comment.