Pooled Singleton Design Pattern

JavaScript
Today when we’re building a software we need to connect to other system such as database or external web services. In many cases we are lucky because we have a module or library that help us, but we pay attention on how we use it. A part the technology stack we use, we need to start to make a consideration about performance and memory usage. I think that we have to avoid to instance object indiscriminately especially for the critical part of  the application. In most case to accomplish our purpose we use Singleton Design Pattern [ref.], but sometimes this is not sufficient because for example we need two or more instances of some module, class or library because for example we need to connect to multiple services so in these case we could use what i call Pooled Singleton Design Pattern.

Diagram for Pooled Singleton Design Pattern

PooledSingletonDesingPattern

In this schema ResourceHandler represent a class, module or library that in general handle some resource (Es. database connection, connection to external services) and we need only limited number instances of them. PooledResources throw his method getResource return a named instance of some ResouceHandler, this element is the key element to accomplish our purpose. So let’s start to realize this two element in practice using JavaScript and in particular Node.js stack technology.

Code example

An example for Pooled Singleton Design Pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'use strict';
module.exports = class ResourceHandler {
 
    constructor(opts = {host: 'localhost', port: 3001, protocol: 'http'}) {
        console.log("Build my resouce handler");
        this.host = opts.host;
        this.port = opts.port;
        this.protocol = opts.protocol;
    }
 
    getConnection() {
        return new Promise((resolve, reject) => {
            console.log(`Connecting to: ${this.protocol}://${this.host}:${this.port}...`);
            setTimeout(
                () => {
                    resolve(`Connected to: ${this.protocol}://${this.host}:${this.port}`);
                },
                3000);
        });
    }
};
1
2
3
4
5
6
7
8
9
10
'use strict';
 
class PooledResourceError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
  }
}
 
module.exports = PooledResourceError
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
 'use strict';
 
const _ = require('underscore');
const ResourceHandler = require('./ResourceHandler');
const PooledResourceError = require('./errors/PooledResourceError');
 
function checkInstance(resourceName, resourceHandlerInstances) {
  return (_.findWhere(resourceHandlerInstances, {name: resourceName}));
};
 
const resourceHandlerInstances = [];
 
module.exports.createResource = function createResource(resourceName, options = {}) {
  if (!resourceName || resourceName.length === 0) {
    throw new PooledResourceError("Missing or invalid parameter resourceName.");
  }
  if(resourceHandlerInstances.length !== 0 &&;
     checkInstance(resourceName, resourceHandlerInstances)) {
    throw new PooledResourceError("Invalid parameter resourceName. There is already" +
      " another resource with the same name.");
  }
  const resourceInstance = {
    name: resourceName,
    instance: new ResourceHandler(options)
  };
  resourceHandlerInstances.push(resourceInstance);
};
 
module.exports.getInstance = function getInstance(resourceName) {
  if (!resourceName || resourceName.lenght === 0 ||
      (!checkInstance(resourceName, resourceHandlerInstances))) {
    throw new PooledResourceError("Parameter resourceName have to be a valid resource name.");
  }
  return (_.findWhere(resourceHandlerInstances, {name: resourceName})).instance;
};
 
module.exports.getResource = function getResource(resourceName, options) {
    if (!resourceName || resourceName.length === 0) {
      throw new PooledResourceError("Missing or invalid parameter resourceName.");
    }
    if (!options) {
        if ((!checkInstance(resourceName, resourceHandlerInstances))) {
          throw new PooledResourceError("Parameter resourceName have to be a valid resource name.");
        }
    } else {
        if(resourceHandlerInstances.length !== 0 &&
           checkInstance(resourceName, resourceHandlerInstances)) {
          throw new PooledResourceError("Invalid parameter resourceName. There is already" +
            " another resource with the same name.");
        }
        const resourceInstance = {
          name: resourceName,
          instance: new ResourceHandler(options)
        };
        resourceHandlerInstances.push(resourceInstance);
    }
    return (_.findWhere(resourceHandlerInstances, {name: resourceName})).instance;
}

So now we can create our single resource or use it as we need some example is reported in the code below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
'use strict';
 
const PooledResource = require('./lib/PooledResources');
 
const optsResA = {
    host: '192.168.0.1',
    port: 3002,
    protocol: 'http'
};
 
const optsResB = {
    host: '192.168.0.2',
    port: 3003,
    protocol: 'https'
};
 
const optsRes3 = {
    host: '192.168.0.3',
    port: 3004,
    protocol: 'http'
};
 
////////////////////////////////////////////////////////////////////////////////
 
PooledResource.createResource('ResA', optsResA);
PooledResource.createResource('ResB', optsResB);
 
PooledResource
.getInstance('ResA')
.getConnection()
.then((res) => {
    console.log(res);
    return PooledResource
    .getInstance('ResB')
    .getConnection();
})
.then((res) => {
    console.log(res);
    return PooledResource
    .getResource('Res3', optsRes3)
    .getConnection();
})
.then((res) => {
    console.log(res);
})
.catch((err) => {
    console.log('Oops something wrong happened.', err);
});
 
////////////////////////////////////////////////////////////////////////////////
Coclusion

In the code PooledResource has the responsability to build new ResourceHandler or retrieve one that is in our resourceHandlerInstances. In this way we can create some objects through its options and retrieve them everywhere in the application, we only import PooledResource and call getResource method passing it the name of the resource. If we have the need to separate the creational process of the object from the the retrieve and return instance of it we can create two separate methods createResource and getInstance and use it in separate manner. In this way we have control over the objects those populate our application and consequently the control over memory usage and performance. All the code reported in this article is present on GitHub.

@NickNaso

0 commenti

Lascia un Commento

Vuoi partecipare alla discussione?
Fornisci il tuo contributo!

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *