Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

What's New in Hazelcast Node.js Client

DZone's Guide to

What's New in Hazelcast Node.js Client

[sponsor] With the release of the latest Hazelcast Node.js client comes a slew of new features. Read on to find out more and how to use them!

· Database Zone
Free Resource

Read why times series is the fastest growing database category.

Rejoice, JavaScript people! There are many new features in Hazelcast Node.js Client 0.4.1. In the 10 minutes you spend reading this blog I will cover “what’s new”! Also, if you read this blog post till the end, you will also learn what’s coming to future releases.

Table of Contents

  • What’s new
    • Serialization and Interoperability with other Hazelcast clients.
    • Predicates.
    • MultiMap, Set, List.
    • Distributed Lock.
    • Messaging with Queue.
    • Data Affinity.
  • Sneak Peek of v0.5 and beyond
    • Even more Data Structures!
    • Fresh Meat.
  • Resources.

It has been a while since out first blog post on Hazelcast Node.js Client [1]. Our brave developers have been very busy adding new features and making Hazelcast Node.js Client faster!

If you haven’t read previous Getting Started blog post, it’s a perfect opportunity to press Pause button now. That “Getting started” post contains all required information to get the Hazelcast Node.js Client gears moving.

What’s New?

The code example in this article were tested under node v6.3.1 (npm v3.10.3) with Hazelcast Client 0.4.1. Many examples contain modern EcmaScript 6 syntax constructs, like aka “far arrow” and etc.

Alright. Because our previous announcement of v0.2, we added the most of the frequently used features of Hazelcast to Node.js client. Including:

  • Distributed Map aka IMap with support of Predicates and Entry Processors.
  • MultiMap.
  • Set.
  • List.
  • Distributed Locks aka ILock.
  • Queue.

Also, we brought full interoperability with other Hazelcast clients.

Let’s have a quick look at this first because:

I don’t know how to put this, but I'm kind of a big deal.

— Ron Burgundy Anchorman: The Legend of Ron Burgundy

Serialization and Interoperability With Other Clients

Hazelcast Node.js client now supports all native serialization techniques that Hazelcast supports. Meaning you can just connect your Node.js client to your working Hazelcast cluster and read what is already there, put new objects and read them from other clients. The fact that Node.js client supports Hazelcast native serialization means it is also fully compatible with all available client languages. The client serializes string, number, and array data types automatically. This makes text-based serialization formats like JSON or XML suitable candidate for a message. Developers can provide serializers for custom objects.

For example, a Java application uses Hazelcast Java Client to store the results of a long-running computation (like Map/Reduce job), and Node.js, .NET or Python applications can consume the results for displaying for the web (Node.js), in Rich Desktop Application (.NET / C#) or for further research and data science with Python math packages.

To demonstrate this approach, let’s write a Java ⇐⇒ Node.js application. We will use a Person object. It has three properties — firstName, lastName, and age.

Person.java

public class Person implements IdentifiedDataSerializable { (1)
    String firstName;
    String lastName;
    int age;

    public Person() {
    }

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    @Override
    public void writeData(ObjectDataOutput out) throws IOException { (2)
        out.writeUTF(firstName);
        out.writeUTF(lastName);
        out.writeInt(age);
    }

    @Override
    public void readData(ObjectDataInput in) throws IOException { (3)
        this.firstName = in.readUTF();
        this.lastName = in.readUTF();
        this.age = in.readInt();
    }

    @Override public int getFactoryId() {
        return 42;
    }

    @Override public int getId() {
        return 42;
    }
}


  1. A Person object implements IdentifiedDataSerializable — fast serialization from Hazelcast.

  2. A writeData method defines how property values will be written to the binary output.

  3. A readData method defines how values can be retrieved from binary input.

Make sure you read from input in the same order you as you wrote to the binary output. Detailed description of IdentifiedDataSerializable methods can be found in the «Serialization» section of the Hazelcast Documentation.

Let’s look what a JavaScript counterpart object looks like:

class Person {
  constructor(firstName, lastName, age) {   // <1>
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  getFactoryId() {
    return 42;
  }

  getClassId() {
    return 42;
  }

  writeData(dataOutput) {   // <2>
    dataOutput.writeUTF(this.firstName);
    dataOutput.writeUTF(this.lastName);
    dataOutput.writeInt(this.age);
  }

  readData(dataInput) {     // <3>
    this.firstName = dataInput.readUTF();
    this.lastName = dataInput.readUTF();
    this.age = dataInput.readInt();
  }
}


  1. JavaScript doesn’t have interfaces as Java. So, it’s just a JavaScript object.

  2. Similarly to Java object, we need to implement writeData

  3. … and readData methods.

Last step — register DataSerializableFactory in client config object

var config = new Config.ClientConfig();
config.serializationConfig.dataSerializableFactories[42] = {
    create (type) {
        if (type === 42) { // <1>
            return new Person();
        }
    }
};


  1. Based on typeId, Hazelcast will figure out what object will be restored from the binary data.

You can checkout Serialization Section and Node.js documentation about how to register custom serializers.

Predicates

Hazelcast IMap is essentially an key-value store. And usually, a developer uses the keys to retrieve data. But in certain cases, a developer doesn’t know a key. Or when a developer needs to find many entries satisfy a condition from a distributed Map. In this case, you needed to retrieve all entries from that map and filter them on the client side. But this method leads to the substantial amount of network communion. If you are looking for a small subset of the entries, it is more efficient to retrieve only the entries you are looking for using newly introduced predicates.

Let’s say you keep ages of people in a Hazelcast map.

map.putAll(['Alice', 34], ['Joe', 22], ['George', 27]);

You can quickly retrieve entries of people that are older than 25 with following code snippet

const Predicates = require('hazelcast-client').Predicates;
map.entrySetWithPredicate(Predicates.greaterThan('this', 25))
  .then((people) => {
    people.forEach(person => console.log(`Person: ${person[0]}, age: ${person[1]}`));
  });

Above snippet will print names and ages of Alice and George.

If you only need their names but not ages,

map.keySetWithPredicate(Predicates.greaterThan('this', 25));

will return only names of Alice and George.

You can find a full list of available predicates at API docs.

MultiMap, Set, List

MultiMap is a particular version of a Map that supports multiple values associated with a single key.

The restaurants MultiMap:

let mmap = hazelcastClient.getMultiMap('restaurants');      // <1>
mmap.put('New York', 'Red Lobster')     // <2>
  .then(() => mmap.put('New York', 'Eataly'))
  .then(() => mmap.get('New York'))
  .then(list => console.log(list));

mmap.put('Las Vegas', 'Burgr')      // <3>
  .then(() => mmap.put('Las Vegas', 'Alibi'))
  .then(() => mmap.put('Las Vegas', 'Pub & Grill'))
  .then(() => mmap.get('Las Vegas'))
  .then(list => console.log(list));
  1. In this example, we have MultiMap of restaurants.

  2. Name of the city used as a key — New York or Las Vegas.

  3. When we need to get a collection of entries. Hazelcast MultiMap supports two types of values – Set (doesn’t allow duplicates, default) and List (preserves order). It can be configured using cluster config object.

The output looks like follows:

[ 'Eataly', 'Red Lobster' ]
[ 'Pub & Grill', 'Alibi', 'Burgr' ]


You can find more info here – MultiMap API.

Distributed Lock

If you need to synchronize your data access through the cluster, Hazelcast’s distributed lock implementation will come useful.

let globalLock = client.getLock('global-lock');

globalLock.lock();
// you can do some job here which doesn't allow shared access
globalLock.unlock();


All supported lock operations are listed in ILock API.

Messaging With Queue

Hazelcast distributed queue enables all cluster members and client to interact with it. Using Hazelcast distributed queue, you can add an item from one client and read it from another. FIFO ordering will apply to all queue operations across the cluster.

Client 1: Consumer of tasks.

let logger = hazelcastClient.loggingService;
let queue = hazelcastClient.getQueue('tasks');

// slow consumer
setInterval(() => {
    queue.take().then(task => logger.info("Consumer", `executing task: ${task}`));
}, 1000);


Client 2: Producer of tasks.

let logger = hazelcastClient.loggingService;
let queue = hazelcastClient.getQueue('tasks');

// fast producer
setInterval(() => {
    var task = tasks[Math.floor(Math.random() * tasks.length)];
    logger.info("Producer", `publishing task: ${task}`);
    queue.offer(task);
}, 500);


In this example, Hazelcast’s uses a «buffer» to separate a fast producer from a slow consumer and this prevents consumer overloading.

Data Affinity

One of the things that we brought to v0.4.1 is ability increase locality of computations and data access on a cluster. Developers will be able to control on which partitions each key is stored. It is only a matter of adding a getPartitionKey() function to user objects.

A developer needs to implement getPartitionKey method:

'use strict';
let Client = require('hazelcast-client').Client;

class Company {  // <1>
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }

  getName() {
    return this.name;
  }
}

class Associate {   // <2>
  constructor(firstName, lastName, companyName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.companyName = companyName;
  }

  getCompanyName() {
    return this.companyName;
  }
}

class PartitionAwareKey {
  constructor(key, partitionKey) {
    this.key = key;
    this.partitionKey = partitionKey;
  }

  getPartititionKey() { // <3>
    return this.partitionKey;
  }
}

Client.newHazelcastClient().then((hazelcastClient) => {
  let companyMap = hazelcastClient.getMap('companyMap');
  let associateMap = hazelcastClient.getMap('associateMap');
  let partitionService = hazelcastClient.getPartitionService();

  let company = new Company('IBM', 'Armonk, North Castle, NY'); 
  let associate = new Associate('John', 'Smith', company.getName());

  let key1 = new PartitionAwareKey('k1', company.getName());    // <4>
  let key2 = new PartitionAwareKey('a1', associate.getCompanyName());   // <4>

  console.log(partitionService.getPartitionId(key1));   // <5>
  console.log(partitionService.getPartitionId(key2));   // <5>

  companyMap.set(key1, company).then(() => associateMap.set(key2, associate));  // <6>

});


  1. An object Company contains name and address of a company.

  2. An object Associate contains info about company’s employee.

  3. PartitionAwareKey (sort of a composite key) should have getPartitionId method that Hazelcast will use to collocate related data.

  4. A companyName property used as partition id.

  5. A partitionId for both keys will be the same…

  6. …meaning «John Smith» and «IBM» will be co-located on the same partition.

Having selected key partition explicitly, users can benefit from on the cluster processing of entries using EntryProcessor’s. Entry processor eliminates the cost of transferring entries between cluster and clients back and forth for simple transformations.

To learn more about Data Affinity in Hazelcast, check official documentation.

Sneak Peek of v0.5 and Beyond

Even More Data Structures!

New release of Node.js client will introduce new data structures such as RingBuffer and Topic. These data structures are suitable for implementing pub-sub use cases. Together with Queue, RB and Topic enable messaging capabilities for your application. Check Messaging with Queue section for peer-to-peer communication example.

Fresh Meat

Even though v0.5 is not released yet, you don’t have to wait for to try these new features. You can build the client locally.

Or, thanks to NPM, install Hazelcast Client from master branch:

npm install git+https://git@github.com:hazelcast/hazelcast-nodejs-client.git

The feedback and pull requests are greatly appreciated.

Resources

As always, please, stay in touch. There are a bunch of ways to provide feedback:

It is a community chat/forum but not a support portal. We can help with answering the questions, and provide pointers but we’re not going to write code for you. We are encouraging people in the community to share the knowledge, please, don’t abuse it. If you’re interested in 24/7 support, we have a dedicated support portal available on commercial terms. Contact sales at Hazelcast dot com to learn more.

Learn how to get 20x more performance than Elastic by moving to a Time Series database.

Topics:
distributed ,features ,node.js ,post ,hazelcast ,client ,config ,code ,example

Published at DZone with permission of Vik Gamov, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}