unleash

package module
v3.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 7, 2020 License: Apache-2.0 Imports: 19 Imported by: 0

README

Build Status GoDoc Go Report Card

unleash-client-go

Unleash Client for Go. Read more about the Unleash project

Version 3.x of the client requires unleash-server v3.x or higher.

Go Version

The client is currently tested against Go 1.10.x and 1.13.x. These versions will be updated as new versions of Go are released.

The client may work on older versions of Go as well, but are not actively tested.

Getting started

1. Install unleash-client-go

To install the latest version of the client use:

go get github.com/akuracy/unleash-client-go/v3

If you are still using Unleash Server v2.x.x, then you should use:

go get github.com/akuracy/unleash-client-go
2. Initialize unleash

The easiest way to get started with Unleash is to initialize it early in your application code:

import (
	"github.com/akuracy/unleash-client-go/v3"
)

func init() {
	unleash.Initialize(
		unleash.WithListener(&unleash.DebugListener{}),
		unleash.WithAppName("my-application"),
		unleash.WithUrl("http://unleash.herokuapp.com/api/"),
	)
}
3. Use unleash

After you have initialized the unleash-client you can easily check if a feature toggle is enabled or not.

unleash.IsEnabled("app.ToggleX")
4. Stop unleash

To shut down the client (turn off the polling) you can simply call the destroy-method. This is typically not required.

unleash.Close()

Built in activation strategies

The Go client comes with implementations for the built-in activation strategies provided by unleash.

  • DefaultStrategy
  • UserIdStrategy
  • GradualRolloutUserIdStrategy
  • GradualRolloutSessionIdStrategy
  • GradualRolloutRandomStrategy
  • RemoteAddressStrategy
  • ApplicationHostnameStrategy

Read more about the strategies in activation-strategy.md.

Unleash context

In order to use some of the common activation strategies you must provide a unleash-context. This client SDK allows you to send in the unleash context as part of the isEnabled call:

ctx := context.Context{
    UserId: "123",
    SessionId: "some-session-id",
    RemoteAddress: "127.0.0.1",
}

unleash.IsEnabled("someToggle", unleash.WithContext(ctx))
Caveat

This client uses go routines to report several events and doesn't drain the channel by default. So you need to either register a listener using WithListener or drain the channel "manually" (demonstrated in this example).

Development

Requirements:

  • make
  • golint (go get -u golang.org/x/lint/golint)

Run tests:

make 

Run lint check:

make lint

Run code-style checks:(currently failing)

make strict-check

Run race-tests:

make test-all

Documentation

Overview

Package unleash is a client library for connecting to an Unleash feature toggle server.

See https://github.com/Unleash/unleash for more information.

Basics

The API is very simple. The main functions of interest are Initialize and IsEnabled. Calling Initialize will create a default client and if a listener is supplied, it will start the sync loop. Internally the client consists of two components. The first is the repository which runs in a separate Go routine and polls the server to get the latest feature toggles. Once the feature toggles are fetched, they are stored by sending the data to an instance of the Storage interface which is responsible for storing the data both in memory and also persisting it somewhere. The second component is the metrics component which is responsible for tracking how often features were queried and whether or not they were enabled. The metrics components also runs in a separate Go routine and will occasionally upload the latest metrics to the Unleash server. The client struct creates a set of channels that it passes to both of the above components and it uses those for communicating asynchronously. It is important to ensure that these channels get regularly drained to avoid blocking those Go routines. There are two ways this can be done.

Using the Listener Interfaces

The first and perhaps simplest way to "drive" the synchronization loop in the client is to provide a type that implements one or more of the listener interfaces. There are 3 interfaces and you can choose which ones you should implement:

  • ErrorListener
  • RepositoryListener
  • MetricsListener

If you are only interesting in tracking errors and warnings and don't care about any of the other signals, then you only need to implement the ErrorListener and pass this instance to WithListener(). The DebugListener shows an example of implementing all of the listeners in a single type.

Reading the channels directly

If you would prefer to have control over draining the channels yourself, then you must not call WithListener(). Instead, you should read all of the channels continuously inside a select. The WithInstance example shows how to do this. Note that all channels must be drained, even if you are not interested in the result.

Examples

The following examples show how to use the client in different scenarios.

Example (CustomStrategy)

ExampleCustomStrategy demonstrates using a custom strategy.

package main

import (
	"fmt"
	"github.com/akuracy/unleash-client-go/v3"
	"github.com/akuracy/unleash-client-go/v3/context"
	"strings"
	"time"
)

type ActiveForUserWithEmailStrategy struct{}

func (s ActiveForUserWithEmailStrategy) Name() string {
	return "ActiveForUserWithEmail"
}

func (s ActiveForUserWithEmailStrategy) IsEnabled(params map[string]interface{}, ctx *context.Context) bool {

	if ctx == nil {
		return false
	}
	value, found := params["emails"]
	if !found {
		return false
	}

	emails, ok := value.(string)
	if !ok {
		return false
	}

	for _, e := range strings.Split(emails, ",") {
		if e == ctx.Properties["emails"] {
			return true
		}
	}

	return false
}

// ExampleCustomStrategy demonstrates using a custom strategy.
func main() {
	unleash.Initialize(
		unleash.WithListener(&unleash.DebugListener{}),
		unleash.WithAppName("my-application"),
		unleash.WithUrl("https://unleash.herokuapp.com/api/"),
		unleash.WithRefreshInterval(5*time.Second),
		unleash.WithMetricsInterval(5*time.Second),
		unleash.WithStrategies(&ActiveForUserWithEmailStrategy{}),
	)

	ctx := context.Context{
		Properties: map[string]string{
			"emails": "example@example.com",
		},
	}

	timer := time.NewTimer(1 * time.Second)

	for {
		<-timer.C
		enabled := unleash.IsEnabled("unleash.me", unleash.WithContext(ctx))
		fmt.Printf("feature is enabled? %v\n", enabled)
		timer.Reset(1 * time.Second)
	}

}
Output:

Example (FallbackFunc)

ExampleFallbackFunc demonstrates how to specify a fallback function.

package main

import (
	"fmt"
	"github.com/akuracy/unleash-client-go/v3"
	"github.com/akuracy/unleash-client-go/v3/context"
	"time"
)

const MissingFeature = "does_not_exist"

// ExampleFallbackFunc demonstrates how to specify a fallback function.
func main() {
	unleash.Initialize(
		unleash.WithListener(&unleash.DebugListener{}),
		unleash.WithAppName("my-application"),
		unleash.WithUrl("http://unleash.herokuapp.com/api/"),
	)

	fallback := func(feature string, ctx *context.Context) bool {
		return feature == MissingFeature
	}

	timer := time.NewTimer(1 * time.Second)

	for {
		<-timer.C
		isEnabled := unleash.IsEnabled(MissingFeature, unleash.WithFallbackFunc(fallback))
		fmt.Printf("'%s' enabled? %v\n", PropertyName, isEnabled)
		timer.Reset(1 * time.Second)
	}
}
Output:

Example (SimpleUsage)

ExampleSimpleUsage demonstrates the simplest way to use the unleash client.

package main

import (
	"fmt"
	"github.com/akuracy/unleash-client-go/v3"
	"time"
)

const PropertyName = "eid.enabled"

// ExampleSimpleUsage demonstrates the simplest way to use the unleash client.
func main() {
	unleash.Initialize(
		unleash.WithListener(&unleash.DebugListener{}),
		unleash.WithAppName("my-application"),
		unleash.WithUrl("http://unleash.herokuapp.com/api/"),
	)

	timer := time.NewTimer(1 * time.Second)

	for {
		<-timer.C
		fmt.Printf("'%s' enabled? %v\n", PropertyName, unleash.IsEnabled(PropertyName))
		timer.Reset(1 * time.Second)
	}

}
Output:

Example (WithInstance)

ExampleWithInstance demonstrates how to create the client manually instead of using the default client. It also shows how to run the event loop manually.

package main

import (
	"fmt"
	"github.com/akuracy/unleash-client-go/v3"
	"time"
)

// Sync runs the client event loop. All of the channels must be read to avoid blocking the
// client.
func Sync(client *unleash.Client) {
	timer := time.NewTimer(1 * time.Second)
	for {
		select {
		case e := <-client.Errors():
			fmt.Printf("ERROR: %v\n", e)
		case w := <-client.Warnings():
			fmt.Printf("WARNING: %v\n", w)
		case <-client.Ready():
			fmt.Printf("READY\n")
		case m := <-client.Count():
			fmt.Printf("COUNT: %+v\n", m)
		case md := <-client.Sent():
			fmt.Printf("SENT: %+v\n", md)
		case cd := <-client.Registered():
			fmt.Printf("REGISTERED: %+v\n", cd)
		case <-timer.C:
			fmt.Printf("ISENABLED: %v\n", client.IsEnabled("eid.enabled"))
			timer.Reset(1 * time.Second)
		}
	}
}

// ExampleWithInstance demonstrates how to create the client manually instead of using the default client.
// It also shows how to run the event loop manually.
func main() {

	// Create the client with the desired options
	client, err := unleash.NewClient(
		unleash.WithAppName("my-application"),
		unleash.WithUrl("http://unleash.herokuapp.com/api/"),
	)

	if err != nil {
		fmt.Printf("ERROR: Starting client: %v", err)
		return
	}

	Sync(client)
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Close

func Close() error

Close will close the default client.

func Initialize

func Initialize(options ...ConfigOption) (err error)

Initialize will specify the options to be used by the default client.

func IsEnabled

func IsEnabled(feature string, options ...FeatureOption) bool

IsEnabled queries the default client whether or not the specified feature is enabled or not.

func WaitForReady

func WaitForReady()

WaitForReady will block until the default client is ready or return immediately.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a structure representing an API client of an Unleash server.

func NewClient

func NewClient(options ...ConfigOption) (*Client, error)

NewClient creates a new client instance with the given options.

func (*Client) Close

func (uc *Client) Close() error

Close stops the client from syncing data from the server.

func (*Client) Count

func (uc *Client) Count() <-chan metric

Count returns the count channel which gives an update when a toggle has been queried.

func (*Client) Errors

func (uc *Client) Errors() <-chan error

Errors returns the error channel for the client.

func (*Client) IsEnabled

func (uc *Client) IsEnabled(feature string, options ...FeatureOption) (enabled bool)

IsEnabled queries whether the specified feature is enabled or not.

It is safe to call this method from multiple goroutines concurrently.

func (*Client) ListFeatures

func (uc *Client) ListFeatures() []api.Feature

ListFeatures returns all available features toggles.

func (*Client) Ready

func (uc *Client) Ready() <-chan bool

Ready returns the ready channel for the client. A value will be available on the channel when the feature toggles have been loaded from the Unleash server.

func (*Client) Registered

func (uc *Client) Registered() <-chan ClientData

Registered returns the registered signal indicating that the client has successfully connected to the metrics service.

func (*Client) Sent

func (uc *Client) Sent() <-chan MetricsData

Sent returns the sent channel which receives data whenever the client has successfully sent metrics to the metrics service.

func (*Client) WaitForReady

func (uc *Client) WaitForReady()

WaitForReady will block until the client has loaded the feature toggles from the Unleash server. It will return immediately if the toggles have already been loaded,

It is safe to call this method from multiple goroutines concurrently.

func (*Client) Warnings

func (uc *Client) Warnings() <-chan error

Warnings returns the warnings channel for the client.

type ClientData

type ClientData struct {
	// AppName is the name of the application.
	AppName string `json:"appName"`

	// InstanceID is the instance identifier.
	InstanceID string `json:"instanceId"`

	// Optional field that describes the sdk version (name:version)
	SDKVersion string `json:"sdkVersion"`

	// Strategies is a list of names of the strategies supported by the client.
	Strategies []string `json:"strategies"`

	// Started indicates the time at which the client was created.
	Started time.Time `json:"started"`

	// Interval specifies the time interval (in ms) that the client is using for refreshing
	// feature toggles.
	Interval int64 `json:"interval"`
}

ClientData represents the data sent to the unleash during registration.

type ConfigOption

type ConfigOption func(*configOption)

ConfigOption represents a option for configuring the client.

func WithAppName

func WithAppName(appName string) ConfigOption

WithAppName specifies the name of the application.

func WithBackupPath

func WithBackupPath(backupPath string) ConfigOption

WithBackupPath specifies the path that is passed to the storage implementation for storing the feature toggles locally.

func WithCustomHeaders

func WithCustomHeaders(headers http.Header) ConfigOption

WithCustomHeaders specifies any custom headers that should be sent along with requests to the server.

func WithDisableMetrics

func WithDisableMetrics(disableMetrics bool) ConfigOption

WithDisabledMetrics specifies that the client should not log metrics to the unleash server.

func WithEnvironment

func WithEnvironment(env string) ConfigOption

WithEnvironment specifies the environment

func WithHttpClient

func WithHttpClient(client *http.Client) ConfigOption

WithHttpClient specifies which HttpClient the client should use for making requests to the server.

func WithInstanceId

func WithInstanceId(instanceId string) ConfigOption

WithInstanceId specifies the instance identifier of the current instance. If not provided, one will be generated based on various parameters such as current user and hostname.

func WithListener

func WithListener(listener interface{}) ConfigOption

WithListener allows users to register a type that implements one or more of the listener interfaces. If no listener is registered then the user is responsible for draining the various channels on the client. Failure to do so will stop the client from working as the worker routines will be blocked.

func WithMetricsInterval

func WithMetricsInterval(metricsInterval time.Duration) ConfigOption

WithMetricsInterval specifies the time interval with which the client should upload the metrics data to the unleash server.

func WithRefreshInterval

func WithRefreshInterval(refreshInterval time.Duration) ConfigOption

WithRefreshInterval specifies the time interval with which the client should sync the feature toggles from the unleash server.

func WithStorage

func WithStorage(storage Storage) ConfigOption

WithStorage specifies which storage implementation the repository should use for storing feature toggles.

func WithStrategies

func WithStrategies(strategies ...strategy.Strategy) ConfigOption

WithStrategies specifies which strategies (in addition to the defaults) should be used by the client.

func WithUrl

func WithUrl(url string) ConfigOption

WithUrl specifies the url of the unleash server the user is connecting to.

type DebugListener

type DebugListener struct{}

DebugListener is an implementation of all of the listener interfaces that simply logs debug info to stdout. It is meant for debugging purposes and an example of implementing the listener interfaces.

func (DebugListener) OnCount

func (l DebugListener) OnCount(name string, enabled bool)

OnCount prints to the console when the feature is queried.

func (DebugListener) OnError

func (l DebugListener) OnError(err error)

OnError prints out errors.

func (DebugListener) OnReady

func (l DebugListener) OnReady()

OnReady prints to the console when the repository is ready.

func (DebugListener) OnRegistered

func (l DebugListener) OnRegistered(payload ClientData)

OnRegistered prints to the console when the client has registered.

func (DebugListener) OnSent

func (l DebugListener) OnSent(payload MetricsData)

OnSent prints to the console when the server has uploaded metrics.

func (DebugListener) OnWarning

func (l DebugListener) OnWarning(warning error)

OnWarning prints out warning.

type ErrorListener

type ErrorListener interface {
	// OnError is called whenever the client experiences an error.
	OnError(error)

	// OnWarning is called whenever the client experiences a warning.
	OnWarning(error)
}

ErrorListener defines an interface that be implemented in order to receive errors and warnings from the client.

type FallbackFunc

type FallbackFunc func(feature string, ctx *context.Context) bool

FallbackFunc represents a function to be called if the feature is not found.

type FeatureOption

type FeatureOption func(*featureOption)

FeatureOption provides options for querying if a feature is enabled or not.

func WithContext

func WithContext(ctx context.Context) FeatureOption

WithContext allows the user to provide a context that will be passed into the active strategy for determining if a specified feature should be enabled or not.

func WithFallback

func WithFallback(fallback bool) FeatureOption

WithFallback specifies what the value should be if the feature toggle is not found on the unleash service.

func WithFallbackFunc

func WithFallbackFunc(fallback FallbackFunc) FeatureOption

WithFallbackFunc specifies a fallback function to evaluate a feature toggle in the event that it is not found on the service.

type MetricListener

type MetricListener interface {
	// OnCount is called whenever the specified feature is queried.
	OnCount(string, bool)

	// OnSent is called whenever the server has successfully sent metrics to the server.
	OnSent(MetricsData)

	// OnRegistered is called whenever the client has successfully registered with the metrics server.
	OnRegistered(ClientData)
}

MetricListener defines an interface that can be implemented in order to receive events that are relevant to sending metrics.

type MetricsData

type MetricsData struct {
	// AppName is the name of the application.
	AppName string `json:"appName"`

	// InstanceID is the instance identifier.
	InstanceID string `json:"instanceId"`

	// Bucket is the payload data sent to the server.
	Bucket api.Bucket `json:"bucket"`
}

MetricsData represents the data sent to the unleash server.

type RepositoryListener

type RepositoryListener interface {
	// OnReady is called when the client has loaded the feature toggles from
	// the Unleash server.
	OnReady()
}

RepositoryListener defines an interface that can be implemented in order to receive events that are relevant to the feature toggle repository.

type Storage

type Storage interface {
	// Init is called to initialize the storage implementation. The backupPath
	// is used to specify the location the data should be stored and the appName
	// can be used in naming.
	Init(backupPath string, appName string)

	// Reset is called after the repository has fetched the feature toggles from the server.
	// If persist is true the implementation of this function should call Persist(). The data
	// passed in here should be owned by the implementer of this interface.
	Reset(data map[string]interface{}, persist bool) error

	// Load is called to load the data from persistent storage and hold it in memory for fast
	// querying.
	Load() error

	// Persist is called when the data in the storage implementation should be persisted to disk.
	Persist() error

	// Get returns the data for the specified feature toggle.
	Get(string) (interface{}, bool)

	// List returns a list of all feature toggles.
	List() []interface{}
}

Storage is an interface that can be implemented in order to have control over how the repository of feature toggles is persisted.

Directories

Path Synopsis
internal
api

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL