11.9. Hot Rod Node.js Client

The Hot Rod Node.js client is an asynchronous event-driven client allowing Node.js users to communicate to JBoss Data Grid servers. This client supports many of the features in the Java client, including the ability to execute and store scripts, utilize cache listeners, and receive the full cluster topology.
The asynchronous operation results are represented with Promise instances, allowing the client to easily chain multiple invocations together and centralizing error handling.

11.9.1. Installing the Hot Rod Node.js Client

The Hot Rod Node.js client is included in a separate distribution, and must be downloaded independently of JBoss Data Grid. Follow the below instructions to install this client:

Procedure 11.3. Installing the Hot Rod Node.js Client

  1. Download the jboss-datagrid-<version>-nodejs-client.zip from the Red Hat Customer Portal.
  2. Extract the downloaded archive.
  3. Use npm to install the provided tarball, as seen in the following command:
    npm install /path/to/jboss-datagrid-7.0.0-nodejs-client/infinispan-0.2.0.Final-redhat-1.tgz

11.9.2. Hot Rod Node.js Requirements

In order to use the Hot Rod Node.js the following prerequisites must be met:
  • Node.js version 0.10 or higher.
  • JBoss Data Grid server instance 7.0.0 or higher.

11.9.3. Hot Rod Node.js Basic Functionality

The following example shows how to connect to a JBoss Data Grid server and perform basic operations, such as putting and retrieving data. The following example assumes that a JBoss Data Grid server is available at the default location of localhost:11222:
var infinispan = require('infinispan');

// Obtain a connection to the JBoss Data Grid server
// As no cache is specified all operations will occur on the 'default' cache
var connected = infinispan.client({port: 11222, host: '127.0.0.1'});

connected.then(function (client) {

  // Attempt to put a value in the cache.
  var clientPut = client.put('key', 'value');

  // Retrieve the value just placed
  var clientGet = clientPut.then(
      function() { return client.get('key'); });

  // Print out the value that was retrieved
  var showGet = clientGet.then(
      function(value) { console.log('get(key)=' + value); });
  
  // Disconnect from the server
  return showGet.finally(
      function() { return client.disconnect(); });
}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});
Connecting to a Named Cache

To connect to a specific cache the cacheName attribute may be defined when specifying the location of the JBoss Data Grid server instance, as seen in the following example:

var infinispan = require('infinispan');

// Obtain a connection to the JBoss Data Grid server
// and connect to namedCache
var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'}, {cacheName: 'namedCache'});

connected.then(function (client) {

  // Log the result of the connection
  console.log('Connected to `namedCache`');

  // Disconnect from the server
  return client.disconnect();

}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});
Using Data Sets

In addition to placing single entries the putAll and getAll methods may be used to place or retrieve a set of data. The following example walks through these operations:

var infinispan = require('infinispan');

// Obtain a connection to the JBoss Data Grid server
// As no cache is specified all operations will occur on the 'default' cache
var connected = infinispan.client({port: 11222, host: '127.0.0.1'});

connected.then(function (client) {
  var data = [
    {key: 'multi1', value: 'v1'},
    {key: 'multi2', value: 'v2'},
    {key: 'multi3', value: 'v3'}];

  // Place all of the key/value pairs in the cache
  var clientPutAll = client.putAll(data);

  // Obtain the values for two of the keys
  var clientGetAll = clientPutAll.then(
    function() { return client.getAll(['multi2', 'multi3']); });

  // Print out the values obtained.
  var showGetAll = clientGetAll.then(
    function(entries) {
      console.log('getAll(multi2, multi3)=%s', JSON.stringify(entries));
    }
  );

  // Obtain an iterator for the cache
  var clientIterator = showGetAll.then(
    function() { return client.iterator(1); });

  // Iterate over the entries in the cache, printing the values
  var showIterated = clientIterator.then(
    function(it) {
      function loop(promise, fn) {
        // Simple recursive loop over iterator's next() call
        return promise.then(fn).then(function (entry) {
          return !entry.done ? loop(it.next(), fn) : entry.value;
        });
      }

      return loop(it.next(), function (entry) {
        console.log('iterator.next()=' + JSON.stringify(entry));
        return entry;
      });
    }
  );

  // Clear the cache of all values
  var clientClear = showIterated.then(
    function() { return client.clear(); });

  // Disconnect from the server
  return clientClear.finally(
    function() { return client.disconnect(); });

}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});

11.9.4. Hot Rod Node.js Conditional Operations

The Hot Rod protocol stores metadata in addition to each value associated with the keys. The getWithMetadata will retrieve the value and any associated metadata with the key.
The following example demonstrates utilizing this metadata:
var infinispan = require('infinispan');

// Obtain a connection to the JBoss Data Grid server
// As no cache is specified all operations will occur on the 'default' cache
var connected = infinispan.client({port: 11222, host: '127.0.0.1'});

connected.then(function (client) {

  // Attempt to put a value in the cache if it does not exist
  var clientPut = client.putIfAbsent('cond', 'v0');

  // Print out the result of the put operation
  var showPut = clientPut.then(
      function(success) { console.log(':putIfAbsent(cond)=' + success); });

  // Replace the value in the cache
  var clientReplace = showPut.then(
      function() { return client.replace('cond', 'v1'); } );

  // Print out the result of the replace
  var showReplace = clientReplace.then(
      function(success) { console.log('replace(cond)=' + success); });

  // Obtain the value and metadata
  var clientGetMetaForReplace = showReplace.then(
      function() { return client.getWithMetadata('cond'); });

  // Replace the value only if the version matches
  var clientReplaceWithVersion = clientGetMetaForReplace.then(
      function(entry) {
        console.log('getWithMetadata(cond)=' + JSON.stringify(entry));
        return client.replaceWithVersion('cond', 'v2', entry.version);
      }
  );

  // Print out the result of the previous replace
  var showReplaceWithVersion = clientReplaceWithVersion.then(
      function(success) { console.log('replaceWithVersion(cond)=' + success); });

  // Obtain the value and metadata
  var clientGetMetaForRemove = showReplaceWithVersion.then(
      function() { return client.getWithMetadata('cond'); });

  // Remove the value only if the version matches
  var clientRemoveWithVersion = clientGetMetaForRemove.then(
      function(entry) {
        console.log('getWithMetadata(cond)=' + JSON.stringify(entry));
        return client.removeWithVersion('cond', entry.version);
      }
  );

  // Print out the result of the previous remove
  var showRemoveWithVersion = clientRemoveWithVersion.then(
      function(success) { console.log('removeWithVersion(cond)=' + success)});

  // Disconnect from the server
  return showRemoveWithVersion.finally(
      function() { return client.disconnect(); });

}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});

11.9.5. Hot Rod Node.js Data Sets

The client may specify multiple server addresses when a connection is defined. When multiple servers are defined it will loop through each one until a successful connection to a node is obtained. An example of this configuration is below:
var infinispan = require('infinispan');

// Accepts multiple addresses and fails over if connection not possible
var connected = infinispan.client(
  [{port: 99999, host: '127.0.0.1'}, {port: 11222, host: '127.0.0.1'}]);

connected.then(function (client) {

  // Obtain a list of all members in the cluster
  var members = client.getTopologyInfo().getMembers();

  // Print out the list of members
  console.log('Connected to: ' + JSON.stringify(members));

  // Disconnect from the server
  return client.disconnect();

}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});

11.9.6. Hot Rod Node.js Remote Events

The Hot Rod Node.js client supports remote cache listeners, and these may be added using the addListener method. This method takes the event type (create, modify, remove, or expiry) and the function callback as parameter. For more information on Remote Event Listeners refer to Section 8.5, “Remote Event Listeners (Hot Rod)”. An example of this is shown below:
var infinispan = require('infinispan');
var Promise = require('promise');

var connected = infinispan.client({port: 11222, host: '127.0.0.1'});

connected.then(function (client) {

  var clientAddListenerCreate = client.addListener(
    'create', function(key) { console.log('[Event] Created key: ' + key); });

  var clientAddListeners = clientAddListenerCreate.then(
    function(listenerId) {
      // Multiple callbacks can be associated with a single client-side listener.
      // This is achieved by registering listeners with the same listener id
      // as shown in the example below.
      var clientAddListenerModify = client.addListener(
        'modify', function(key) { console.log('[Event] Modified key: ' + key); },
        {listenerId: listenerId});

      var clientAddListenerRemove = client.addListener(
        'remove', function(key) { console.log('[Event] Removed key: ' + key); },
        {listenerId: listenerId});

      return Promise.all([clientAddListenerModify, clientAddListenerRemove]);
    });

  var clientCreate = clientAddListeners.then(
    function() { return client.putIfAbsent('eventful', 'v0'); });

  var clientModify = clientCreate.then(
    function() { return client.replace('eventful', 'v1'); });

  var clientRemove = clientModify.then(
    function() { return client.remove('eventful'); });

  var clientRemoveListener =
        Promise.all([clientAddListenerCreate, clientRemove]).then(
          function(values) {
            var listenerId = values[0];
            return client.removeListener(listenerId);
          });

  return clientRemoveListener.finally(
    function() { return client.disconnect(); });

}).catch(function(error) {

  console.log("Got error: " + error.message);

});

11.9.7. Hot Rod Node.js Working with Clusters

JBoss Data Grid server instances may be clustered together to provide failover and capabilities for scaling up. While working with a cluster is very similar to using a single instance there are a few considerations:
  • The client only needs to know about a single server's address to receive information about the entire server cluster, regardless of the cluster size.
  • For distributed caches, key-based operations are routed in the cluster using the same consistent hash algorithms used by the server. This means that the client can locate where any particular key resides without the need for extra network hops.
  • For distributed caches, multi-key or key-less operations routed in round-robin fashion.
  • For replicated and invalidated caches, all operations are routed in round-robin fashion, regardless of whether they are key-based or multi-key/key-less.
All routing and failover is transparent to the client, so operations executed against a cluster look identical to the code examples performed above.
The cluster topology can be obtained using the following example:
var infinispan = require('infinispan');

var connected = infinispan.client({port: 11322, host: '127.0.0.1'});

connected.then(function (client) {

  var members = client.getTopologyInfo().getMembers();

  // Should show all expected cluster members
  console.log('Connected to: ' + JSON.stringify(members));

  // Add your own operations here...

  return client.disconnect();

}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});