Getting Started with Hazelcast Go Client

What You’ll Learn

This tutorial will get you started with the Hazelcast Go client.

Before you Begin

  • A text editor or IDE

  • Docker

  • Go version 1.15 and above

Start a Hazelcast Member

We will use the 5.0 version of Hazelcast for this tutorial.

In this tutorial, we will use Docker for simplicity. You can find the different installation methods here.

docker run --name hazelcast -p 5701:5701 hazelcast/hazelcast:5.0

This will start a new Hazelcast member at port 5701. Now, we have a Hazelcast cluster with just one member.

Start using Hazelcast Go Client

Create a new folder and navigate to it:

mkdir go-client-getting-started
cd go-client-getting-started

Initialize a new go module to start working on with any module name you prefer:

go mod init example

Add Hazelcast Go Client’s latest version as a dependency:

go get github.com/hazelcast/hazelcast-go-client@latest

We are ready to start experimenting with Go Client. Let’s create a go file:

touch map.go

Copy and paste the following code:

package main

import (
        "context"
        "fmt"
        "log"

        "github.com/hazelcast/hazelcast-go-client"
)

func main() {
    ctx := context.TODO()
    // create the client and connect to the cluster
    client, err := hazelcast.StartNewClient(ctx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(client.Name())
}

To run this go project, use the following command:

go run map.go

The following line creates and starts a new Hazelcast client with the default configuration.

client, err := hazelcast.StartNewClient(ctx)

The client automatically connects to the Hazelcast member available on the local machine, in our case the docker instance.

Use a Map

A Hazelcast map is a distributed key-value store, similar to built-in map type in Go. You can store key-value pairs in a map. In the following example, we will work with map entries where the keys are session ids and the values are emails.

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/hazelcast/hazelcast-go-client"
)

func main() {
    ctx := context.TODO()
    client, err := hazelcast.StartNewClient(ctx)
    if err != nil {
        log.Fatal(err)
    }
    // get a map
    emails, err := client.GetMap(ctx, "emails")
    if err != nil {
        log.Fatal(err)
    }
    // set a value in the map
    if err := emails.Set(ctx, "sid12345", "example1@email.com"); err != nil {
        log.Fatal(err)
    }
    if err := emails.Set(ctx, "sid12346", "example2@email.com"); err != nil {
        log.Fatal(err)
    }
    // get a value from the map
    email1, err := emails.Get(ctx, "sid12345")
    if err != nil {
        log.Fatal(err)
    }
    email2, err := emails.Get(ctx, "sid12346")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(email1)
    fmt.Println(email2)
    // stop the client to release resources
    client.Shutdown(ctx)
}

The output of this snippet is given below:

example1@email.com
example2@email.com

The following line returns a map proxy for the 'emails' map:

emails, err := client.GetMap(ctx, "emails")

If the map called “emails” does not exist in the Hazelcast cluster, it will be automatically created. All the clients that connect to the same cluster will have access to the same map.

With these lines, the Go client adds data to the map. The first parameter is context. Go client supports the Go context package. Most methods have the context as the first parameter. Check out the Go context documentation here. The second parameter is the key of the entry, the third one is the value:

 emails.Set(ctx, "sid12345", "example1@email.com")
 emails.Set(ctx, "sid12346", "example2@email.com")

Finally, we get the values we added to the map with the get method:

email1, err := emails.Get(ctx, "sid12345")
email2, err := emails.Get(ctx, "sid12346")

Add a Listener to the Map

You can add an entry listener using the “AddEntryListener” method available on map proxy. This will allow you to listen to certain events that happen in the map across the cluster.

The first argument being a context instance as we discussed before, the second argument to the “AddEntryListener” method is a configuration of type "MapEntryListenerConfig". This contains options to filter the events by key and/or predicate and has an option to include the value of the entry, not just the key. You should also choose which type of events you want to receive. In this example, we registered listeners for “added”, “removed" and “updated” events and we listen for all of the keys. Third argument is a function parameter that is called every time an enabled event is received. In this example we implement a switch-case to differantiate event types.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/hazelcast/hazelcast-go-client"
)

func main() {
	// error handling was omitted for brevity
	ctx := context.TODO()
	client, err := hazelcast.StartNewClient(ctx)
	if err != nil {
		log.Fatal(err)
	}
	entryListenerConfig := hazelcast.MapEntryListenerConfig{
		IncludeValue: true,
	}
	emails, err := client.GetMap(ctx, "emails")
	if err != nil {
		log.Fatal(err)
	}
	emails.Clear(ctx)
	// enable receiving entry added events
	entryListenerConfig.NotifyEntryAdded(true)
	// enable receiving entry removed events
	entryListenerConfig.NotifyEntryRemoved(true)
	// enable receiving entry updated events
	entryListenerConfig.NotifyEntryUpdated(true)
	subscriptionID, err := emails.AddEntryListener(ctx, entryListenerConfig, func(event *hazelcast.EntryNotified) {
		switch event.EventType {
		// this is an entry added event
		case hazelcast.EntryAdded:
			fmt.Println("Entry Added:", event.Value)
		// this is an entry removed event
		case hazelcast.EntryRemoved:
			fmt.Println("Entry Removed with key:", event.Key)
		// this is an entry updated event
		case hazelcast.EntryUpdated:
			fmt.Println("Entry Updated from", event.Value, "to", event.OldValue)
		}
	})
	if err != nil {
		log.Fatal(err)
	}
	if err := emails.Set(ctx, "sid12345", "example1@email.com"); err != nil {
		log.Fatal(err)
	}
	if err := emails.Set(ctx, "sid12346", "example2@email.com"); err != nil {
		log.Fatal(err)
	}
	email1, err := emails.Get(ctx, "sid12345")
	if err != nil {
		log.Fatal(err)
	}
	email2, err := emails.Get(ctx, "sid12346")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Email1:", email1)
	fmt.Println("Email2:", email2)

	if err := emails.Delete(ctx, "sid12345"); err != nil {
		log.Fatal(err)
	}
	if err := emails.Set(ctx, "sid12346", "example1@email.com"); err != nil {
		log.Fatal(err)
	}

	email1, err = emails.Get(ctx, "sid12345")
	if err != nil {
		log.Fatal(err)
	}
	email2, err = emails.Get(ctx, "sid12346")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Email1:", email1)
	fmt.Println("Email2:", email2)

	// you can use the subscriptionID later to remove the event listener.
	if err := emails.RemoveEntryListener(ctx, subscriptionID); err != nil {
		log.Fatal(err)
	}
}

First, the map is cleared to fire events even if there are some entries in the map. Then, two session entries are added, and they are logged. After that, we remove one of the entries and update the other one. Then, we log the session entries again.

The output is as follows:

Entry Added: example1@email.com
Entry Added: example2@email.com
Email1: example1@email.com
Email2: example2@email.com
Entry Removed with key: sid12345
Entry Updated from example1@email.com to example2@email.com
<nil>
example1@email.com

The value of the first entry becomes “nil” since it is removed.

Cleanup

docker rm -f hazelcast

Summary

In this tutorial, you learned how to get started with Hazelcast Go Client using a distributed map.

See Also

There are a lot of things that you can do with the Go client. For more, such as how you can query a map with predicates, check out our Go client repository. or jump straight to the documentation.

If you have any questions, suggestions, or feedback please do not hesitate to reach out to us via Hazelcast Community Slack. Also, please take a look at the issue list if you would like to contribute to the client.