Giter Site home page Giter Site logo

unleash / unleash-client-java Goto Github PK

View Code? Open in Web Editor NEW
115.0 28.0 68.0 19.17 MB

Unleash client SDK for Java

Home Page: https://docs.getunleash.io

License: Apache License 2.0

Java 100.00%
toggles feature-toggles feature-flags java feature unleash-server java-client client-sdk activation-strategy feature-management

unleash-client-java's Introduction

Unleash Client SDK for Java

This is the Unleash Client SDK for Java. It is compatible with the Unleash-hosted.com SaaS offering and Unleash Open-Source.

Build Status Coverage Status Maven Central

Getting started

This section shows you how to get started quickly and explains some common configuration scenarios. For a full overview of Unleash configuration options, check out the Configuration options section.

Step 1: Install the Unleash Java SDK

You need to add the Unleash SDK as a dependency for your project. Here's how you would add it to your pom.xml file:

<dependency>
    <groupId>io.getunleash</groupId>
    <artifactId>unleash-client-java</artifactId>
    <version>Latest version here</version>
</dependency>

Step 2: Create a new Unleash instance


⚠️ Important: In almost every case, you only want a single, shared instance of the Unleash class (a singleton) in your application . You would typically use a dependency injection framework (such as Spring or Guice) to inject it where you need it. Having multiple instances of the client in your application could lead to inconsistencies and performance degradation.

To help you detect cases where you configure multiple instances by mistake, the SDK will print an error message if you create multiple instances with the same configuration values. You can also tell Unleash to fail when this happens by setting the constructor parameter failOnMultipleInstantiations to true.


When instantiating an Unleash client, you can choose to do it either synchronously or asynchronously: The SDK will synchronize with the Unleash API on initialization, so it can take a few hundred milliseconds for the client to reach the correct state. This is usually not an issue and Unleash will do this in the background as soon as you initialize it. However, if it's important that you not continue execution until the SDK has synchronized, then you should use the synchronousFetchOnInitialisation option to block the client until it has successfully synchronized with the server.

Example configurations

💡 Tip: Refer to the section on configuration options for a more complete explanation of all the options.

Here's two examples of how you might initialize the Unleash SDK in your applications. The examples use dummy values and are almost identical. The only difference is that the first example is asynchronous, while the second example is synchronous.

Asynchronous initialization example:

UnleashConfig config = UnleashConfig.builder()
        .appName("my.java-app")
        .instanceId("your-instance-1")
        .unleashAPI("<unleash-api-url>")
        .apiKey("<client-api-token>")
        .build();

Unleash unleash = new DefaultUnleash(config);

Synchronous initialization example:

UnleashConfig config = UnleashConfig.builder()
        .appName("my.java-app")
        .instanceId("your-instance-1")
        .unleashAPI("<unleash-api-url>")
        .apiKey("<client-api-token>")
        .synchronousFetchOnInitialisation(true)
        .build();

Unleash unleash = new DefaultUnleash(config);

Step 3: Use the feature toggle

With the SDK initialized, you can use the isEnabled method to check the state of your feature toggles. The method returns a boolean indicating whether a feature is enabled for the current request or not.

if(unleash.isEnabled("AwesomeFeature")) {
  //do some magic
} else {
  //do old boring stuff
}

The isEnabled method also accepts a second, boolean argument. The SDK uses this as a fallback value if it can't find the feature you're trying to check. In other words, unleash.isEnabled("non-existing-toggle") would usually return false (assuming that "non-existing-toggle") doesn't exist). If you instead do unleash.isEnabled("non-existing-toggle", true), then Unleash would return true if it didn't find the toggle.

You can also provide an Unleash context to the isEnabled method. Refer to the Unleash context section for more information about using the Unleash context in the Java SDK.

Activation strategies

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

  • DefaultStrategy
  • UserWithIdStrategy
  • GradualRolloutRandomStrategy
  • GradualRolloutUserWithIdStrategy
  • GradualRolloutSessionIdStrategy
  • RemoteAddressStrategy
  • ApplicationHostnameStrategy

Read more about the strategies in the activation strategies reference documentation.

Custom strategies

You may also specify and implement your own strategy. The specification must be registered in the Unleash UI and you must register the strategy implementation when you wire up unleash.

Strategy s1 = new MyAwesomeStrategy();
Strategy s2 = new MySuperAwesomeStrategy();
Unleash unleash return new DefaultUnleash(config, s1, s2);

Unleash context

In order to use some of the common activation strategies you must provide an Unleash context. This client SDK provides two ways of provide the unleash-context:

1. As part of isEnabled call

This is the simplest and most explicit way of providing the unleash context. You just add it as an argument to the isEnabled call.

UnleashContext context = UnleashContext.builder()
  .userId("[email protected]").build();

unleash.isEnabled("someToggle", context);

2. Via an UnleashContextProvider

This is a more advanced approach, where you configure an Unleash context provider. With a context provider, you don't need to rebuild or pass the Unleash context to every unleash.isEnabled call.

The provider typically binds the context to the same thread as the request. If you use Spring, the UnleashContextProvider will typically be a 'request scoped' bean.

UnleashContextProvider contextProvider = new MyAwesomeContextProvider();

UnleashConfig config = new UnleashConfig.Builder()
            .appName("java-test")
            .instanceId("instance x")
            .unleashAPI("http://unleash.herokuapp.com/api/")
            .apiKey("<client-api-token>")
            .unleashContextProvider(contextProvider)
            .build();

Unleash unleash = new DefaultUnleash(config);

// Anywhere in the code unleash will get the unleash context from your registered provider.
unleash.isEnabled("someToggle");

Custom HTTP headers

If you want the client to send custom HTTP Headers with all requests to the Unleash API you can define that by setting them via the UnleashConfig.

UnleashConfig unleashConfig = UnleashConfig.builder()
                .appName("my-app")
                .instanceId("my-instance-1")
                .unleashAPI(unleashAPI)
                .apiKey("12312Random")
                .customHttpHeader("<name>", "<value>")
                .build();

Dynamic custom HTTP headers

If you need custom http headers that change during the lifetime of the client, a provider can be defined via the UnleashConfig.

public class CustomHttpHeadersProviderImpl implements CustomHttpHeadersProvider {
    @Override
    public Map<String, String> getCustomHeaders() {
        String token = "Acquire or refresh token";
        return new HashMap() {{ put("Authorization", "Bearer "+token); }};
    }
}
CustomHttpHeadersProvider provider = new CustomHttpHeadersProviderImpl();

UnleashConfig unleashConfig = UnleashConfig.builder()
                .appName("my-app")
                .instanceId("my-instance-1")
                .unleashAPI(unleashAPI)
                .apiKey("API token")
                .customHttpHeadersProvider(provider)
                .build();

Subscriber API

(Introduced in 3.2.2)

Sometimes you want to know when Unleash updates internally. This can be achieved by registering a subscriber. An example on how to configure a custom subscriber is shown below. Have a look at UnleashSubscriber.java to get a complete overview of all methods you can override.

UnleashConfig unleashConfig = UnleashConfig.builder()
    .appName("my-app")
    .instanceId("my-instance-1")
    .unleashAPI(unleashAPI)
    .apiKey("API token")
    .subscriber(new UnleashSubscriber() {
        @Override
        public void onReady(UnleashReady ready) {
            System.out.println("Unleash is ready");
        }
        @Override
        public void togglesFetched(FeatureToggleResponse toggleResponse) {
            System.out.println("Fetch toggles with status: " + toggleResponse.getStatus());
        }

        @Override
        public void togglesBackedUp(ToggleCollection toggleCollection) {
            System.out.println("Backup stored.");
        }

    })
    .build();

Options

  • appName - Required. Should be a unique name identifying the client application using Unleash.
  • synchronousFetchOnInitialisation - Allows the user to specify that the unleash-client should do one synchronous fetch to the unleash-api at initialisation. This will slow down the initialisation (the client must wait for a http response). If the unleash-api is unavailable the client will silently move on and assume the api will be available later.
  • disablePolling - Stops the client from polling. If used without synchronousFetchOnInitialisation will cause the client to never fetch toggles from the unleash-api.
  • fetchTogglesInterval - Sets the interval (in seconds) between each poll to the unleash-api. Set this to 0 to do a single fetch and then stop refreshing while the process lives.

HTTP Proxy with Authentication

The Unleash Java client uses HttpURLConnection as HTTP Client which already recognizes the common JVM proxy settings such as http.proxyHost and http.proxyPort. So if you are using a Proxy without authentication, everything works out of the box. However, if you have to use Basic Auth authentication with your proxy, the related settings such as http.proxyUser and http.proxyPassword do not get recognized by default. In order to enable support for basic auth against a http proxy, you can simply enable the following option on the configuration builder:

UnleashConfig config = UnleashConfig.builder()
    .appName("my-app")
    .unleashAPI("http://unleash.org")
    .apiKey("API token")
    .enableProxyAuthenticationByJvmProperties()
    .build();

Toggle fetcher

The Unleash Java client now supports using your own toggle fetcher. The Config builder has been expanded to accept a io.getunleash.util.UnleashFeatureFetcherFactory which should be a Function<UnleashConfig, FeatureFetcher>. If you want to use OkHttp instead of HttpURLConnection you'll need a dependency on okhttp

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.10+</version>
</dependency>

Then you can change your config to

UnleashConfig config = UnleashConfig.builder()
    .appName("my-app")
    .unleashAPI("http://unleash.org")
    .apiKey("API token")
    .unleashFeatureFetcherFactory(OkHttpFeatureFetcher::new)
    .build();

This will then start using OkHttp instead of HttpURLConnection.

Metrics sender

The Unleash Java client now supports using your own metrics sender. The Config builder has been expanded to accept a io.getunleash.util.MetricsSenderFactory which should be a Function<UnleashConfig, MetricsSender>.

If you want to use OkHttp instead of HttpURLConnection you'll need a dependency on okhttp

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.10+</version>
</dependency>

Then you can change your config to

UnleashConfig config = UnleashConfig.builder()
    .appName("my-app")
    .unleashAPI("http://unleash.org")
    .customHttpHeader("Authorization", "API token")
    .unleashMetricsSenderFactory(OkHttpMetricsSender::new)
    .build();

This will then start using OkHttp instead of HttpURLConnection to send metrics

Local backup

By default unleash-client fetches the feature toggles from unleash-server every 10s, and stores the result in unleash-repo.json which is located in the java.io.tmpdir directory. This means that if the unleash-server becomes unavailable, the unleash-client will still be able to toggle the features based on the values stored in unleash-repo.json. As a result of this, the second argument of isEnabled will be returned in two cases:

  • When unleash-repo.json does not exists
  • When the named feature toggle does not exist in unleash-repo.json

Bootstrapping

  • Unleash supports bootstrapping from a JSON string.
  • Configure your own custom provider implementing the ToggleBootstrapProvider interface's single method String read(). This should return a JSON string in the same format returned from /api/client/features
  • Example bootstrap files can be found in the json files located in src/test/resources
  • Our assumption is this can be use for applications deployed to ephemeral containers or more locked down file systems where Unleash's need to write the backup file is not desirable or possible.

Provided Bootstrappers

ToggleBootstrapFileProvider

  • Unleash comes configured with a ToggleBootstrapFileProvider which implements the ToggleBootstrapProvider interface.
  • It is the default implementation used if not overridden via the setToggleBootstrapProvider on UnleashConfig.
Configure ToggleBootstrapFileProvider
  • The ToggleBootstrapFileProvider reads the file located at the path defined by the UNLEASH_BOOTSTRAP_FILE environment variable.
  • It supports both classpath: paths and absolute file paths.

Unit testing

You might want to control the state of the toggles during unit-testing. Unleash do come with a FakeUnleash implementation for doing this.

Some examples on how to use it below:

// example 1: everything on
FakeUnleash fakeUnleash = new FakeUnleash();
fakeUnleash.enableAll();

assertThat(fakeUnleash.isEnabled("unknown"), is(true));
assertThat(fakeUnleash.isEnabled("unknown2"), is(true));

// example 2
FakeUnleash fakeUnleash = new FakeUnleash();
fakeUnleash.enable("t1", "t2");

assertThat(fakeUnleash.isEnabled("t1"), is(true));
assertThat(fakeUnleash.isEnabled("t2"), is(true));
assertThat(fakeUnleash.isEnabled("unknown"), is(false));

// example 3: variants
FakeUnleash fakeUnleash = new FakeUnleash();
fakeUnleash.enable("t1", "t2");
fakeUnleash.setVariant("t1", new Variant("a", (String) null, true));

assertThat(fakeUnleash.getVariant("t1").getName(), is("a"));

Se more in FakeUnleashTest.java

Development

Build:

mvn clean install

Jacoco coverage reports:

mvn jacoco:report

The generated report will be available at target/site/jacoco/index.html

Formatting

Releasing

Deployment

  • You'll need an account with Sonatype's JIRA - https://issues.sonatype.org
  • In addition your account needs access to publish under io.getunleash

GPG signing

  • You'll need gpg installed and a configured gpg key for signing the artifacts

Example settings.xml

  • In ~/.m2/settings.xml put
<settings>
    ...
    <profiles>
        ...
        <profile>
            <id>ossrh</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <gpg.executable>gpg</gpg.executable> <!-- Where to find gpg -->
                <gpg.passphrase>[PASSPHRASE FOR YOUR GPG KEY]</gpg.passphrase>
            </properties>
        </profile>
    </profiles>
    ...
    <servers>
        ...
        <server>
            <id>sonatype-nexus-snapshots</id>
            <username>[YOUR_SONATYPE_JIRA_USERNAME]</username>
            <password>[YOUR_SONATYPE_JIRA_PASSWORD]</password>
        </server>
        <server>
            <id>ossrh</id>
            <username>[YOUR_SONATYPE_JIRA_USERNAME]</username>
            <password>[YOUR_SONATYPE_JIRA_PASSWORD]</password>
        </server>
        <server>
            <id>sonatype-nexus-staging</id>
            <username>[YOUR_SONATYPE_JIRA_USERNAME]</username>
            <password>[YOUR_SONATYPE_JIRA_PASSWORD]</password>
        </server>
    </servers>
</settings>

More information

Configuration options

The UnleashConfig$Builder class (created via UnleashConfig.builder()) exposes a set of builder methods to configure your Unleash client. The available options are listed below with a description of what they do. For the full signatures, take a look at the UnleashConfig class definition.

Method name Description Required Default value
apiKey The api key to use for authenticating against the Unleash API. Yes null
appName The name of the application as shown in the Unleash UI. Registered applications are listed on the Applications page. Yes null
backupFile The path to the file where local backups get stored. No Synthesized from your system's java.io.tmpdir and your appName: "<java.io.tmpdir>/unleash-<appName>-repo.json"
customHttpHeader Add a custom HTTP header to the list of HTTP headers that will the client sends to the Unleash API. Each method call will add a new header. Note: in most cases, you'll need to use this method to provide an API token. No N/A
customHttpHeadersProvider Add a custom HTTP header provider. Useful for dynamic custom HTTP headers. No null
disablePolling A boolean indicating whether the client should poll the unleash api for updates to toggles.
disableMetrics A boolean indicating whether the client should disable sending usage metrics to the Unleash server. No false
enableProxyAuthenticationByJvmProperties Enable support for using JVM properties for HTTP proxy authentication. No false
environment The value to set for the Unleash context's environment property. Not the same as Unleash's environments. No null
fallbackStrategy A strategy implementation that the client can use if it doesn't recognize the strategy type returned from the server. No null
fetchTogglesInterval How often (in seconds) the client should check for toggle updates. Set to 0 if you want to only check once. No 10
instanceId A unique(-ish) identifier for your instance. Typically a hostname, pod id or something similar. Unleash uses this to separate metrics from the client SDKs with the same appName. Yes null
namePrefix If provided, the client will only fetch toggles whose name starts with the provided value. No null
projectName If provided, the client will only fetch toggles from the specified project. (This can also be achieved with an API token). No null
proxy A Proxy object. Use this to configure a third-party proxy that sits between your client and the Unleash server. No null
scheduledExecutor A custom executor to control timing and running of tasks (such as fetching toggles, sending metrics). No UnleashScheduledExecutorImpl
sendMetricsInterval How often (in seconds) the client should send metrics to the Unleash server. Ignored if you disable metrics with the disableMetrics method. No 60
subscriber Register a subscriber to Unleash client events. No null
synchronousFetchOnInitialisation Whether the client should fetch toggle configuration synchronously (in a blocking manner) on initialisation. No false
toggleBootstrapProvider Add a bootstrap provider (must implement the ToggleBootstrapProvider interface) No
unleashAPI The URL of the Unleash API. Yes null
unleashContextProvider An Unleash context provider used to configure Unleash. No null
unleashFeatureFetcherFactory A factory providing a FeatureFetcher implementation. No HttpFeatureFetcher::new
unleashMetricsSenderFactory A factory providing a MetricSender implementation. No DefaultHttpMetricsSender::new
startupExceptionHandler Handler for the behavior in the event of an error when starting the client. No null

When you have set all the desired options, initialize the configuration with the build method. You can then pass the configuration to the Unleash client constructor. As an example:

UnleashConfig config = UnleashConfig.builder()
            .appName("your app name")
            .instanceId("instance id")
            .unleashAPI("http://unleash.herokuapp.com/api/")
            .apiKey("API token")
            // ... more configuration options
            .build();

Unleash unleash = new DefaultUnleash(config);

Other information

  • Check out our guide for more information on how to build and scale feature flag systems

unleash-client-java's People

Contributors

ajvasey avatar andereld avatar andersos avatar andreas-unleash avatar annedroiid avatar blankg avatar checketts avatar chriswk avatar dennis-menge avatar dependabot[bot] avatar emilva avatar enrico2 avatar eurafa avatar gardleopard avatar gastonfournier avatar henrik242 avatar ivarconr avatar jan-berge-ommedal avatar jarib avatar joschi avatar martonskjevelandnav avatar michbeck100 avatar msayag avatar pedro-carneiro avatar praveenpg avatar sighphyre avatar sjaanus avatar sullrich84 avatar sveisvei avatar thomasheartman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unleash-client-java's Issues

Metric Bucket + Client Features Flag list

I noticed no.finn.unleash.metric.MetricsBucket is a non-public class, is that intentional? I was hoping to retrieve the MetricBucket via the UnleashSubscriber.clientMetrics() event to capture a Set of the feature flags the client is checking for. This could be a quick way to identify feature flags that exist (and are being called) in the client, that may not exist on the server yet. Unless there is a better alternative way for the client to know what feature flags exist within the client code base. I know from the Unleash server side, for a given application, I can get a list of Feature flags that have been triggered on the client, but are not defined on the server. I'm trying to do a similar thing, just from the client side.

Synchronous call to pull toggles when instantiating the unleash client

Right now it looks like when the unleash client is instantiated a background thread is setup to query the toggles from the server. I am running into an issue where a call to getFeatureToggleNames() is returning an empty list because the call is made too soon after the instantiation of the client and no backup file existed.

Can the instantiation make an synchronous call to update first and then setup the background thread?

Error fetching toggles from Unleash API (StatusCode: 304)

I have an Unleash server v3.2.1 and java client v3.2.0 on a SpringBoot 2 backend API.

Seems to work well and use the updated value on the backend API but in the console I have this warrning/error every second:

2019-02-18 15:26:20.592  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:21.865  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:22.745  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:23.789  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:23.842  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:24.375  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:25.008  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:26.282  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:26.990  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:27.315  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:27.992  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:28.161  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:29.079  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:29.201  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:30.279  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)
2019-02-18 15:26:30.593  WARN 7514 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Error fetching toggles from Unleash API (StatusCode: 304)

...

Evaluate Testframework

JUnit is old and boring. Unit testing can be more fun with modern framework such as Spock.

I vote for switching to Spock.

What is your favorite test framework in java?

Proxy support that recognizes -Dhttp.proxy* settings

Hello everyone,

I am currently playing around with the unleash java client. It works really well, I only struggled a little bit with the proxy configuration.

As you use HttpUrlConnection as http client, basic auth proxy settings are not recognized, i.e.:

  • http.proxyUser
  • http.proxyPassword

Therefore, it is necessary to implement a custom java.net.Authenticator. What do you think of providing such an implementation within the library itself? If so, I would try to create a PR for that.

Thank you in advance!

Can we make the API more explicit?

The way unleash is implemented now it is not really intuitive for the developer. Instantiating the unleash-client will also set up a background thread used to poll toggles and send metrics back to unleash. We see from time to time this ending up with memory leakage because the developer creates a new unleash instance per request handler.

Can we make the API more explicit by having dedicated methods for starting and stopping unleash?

  • unleash.startPolling()
  • unleash.destroy()

What should happen if the user has not stated unleash yet? Should we then just throw a runtime exception? Or always return false?

java client -fail gracefully

copied from Unleash/unleash#15

The client should not stop the application from starting or working if unleash-server is unavailable or acting up.

Enforce defaults for every feature toggle used by the application.

Create a unleash-mock-implementation to facilitate unit-testing

To allow users of the unleash-client to perform awesome unit-testing in their own application we must implement a simple UnleashMock client with a simple API.

I'm thinking of something like:

UnleashMock unleash = new UnleashMock();

//enable all feature toggles
unleash.enableAll()

//disable all feature toggles
unleash.disableAll()

//enable specific feature toggle(s)
unleash.enable("featureName")
unleash.enable("featureName", "toggle2", "...")

//disable specific feature toggle(s)
unleash.disable("featureName", "toggle2", "...")

ClientRegistration Event - Suggestions

Small change suggestion.
You may want to change getInterval() to getSendMetricInterval() as this could easily be confused with the fetchTogglesInterval value from the UnleashConfig if the source code isn't examined.

Also, I noticed that the ClientRegistration is pulling a several values from the UnleashConfig. Would it make more sense to instead just store the entire UnleashConfig as an event variable instead of the few selected fields? That way, someone implementing the clientRegistered() method of the UnleashSubscriber can pick/choose which fields are relevant for them to log.

client metrics

copied from Unleash/unleash#21

jarib:
There's currently no way of tracking what toggles are being used across applications. The simplest I can think of is to add easy statsd/graphite reporting in the client.

Perhaps look at Yammer Metrics. From reading the Kafka docs it sounds pretty nice:

Kafka uses Yammer Metrics for metrics reporting in both the server and the client. This can be configured to report stats using pluggable stats reporters to hook up to your monitoring system.

hennings:
Codahale Metrics (successor to Yammer metrics) is a nice library for timing
histograms, counters and gauges. There are several publishers you can plug
in to send the data to graphite, a servlet etc.

Henning
On 21 Oct 2014 15:33, "Jari Bakken" [email protected] wrote:

There's currently no way of tracking what toggles are being used across
applications. The simplest I can think of is to add easy statsd/graphite
reporting to the client.

Perhaps look at Yammer Metrics
https://dropwizard.github.io/metrics/3.1.0/. From reading the Kafka
docs it sounds pretty nice:

Kafka uses Yammer Metrics for metrics reporting in both the server and the
client. This can be configured to report stats using pluggable stats
reporters to hook up to your monitoring system.


Reply to this email directly or view it on GitHub
Unleash/unleash#21.

Extend the unleash interface with context-data

In the current version of Unleash strategies needs to get their context data via special provides which typically is implemented as some magic-data available at the current request. This works well in most java-based web-applications.

However doing toggles in a standalone web-application this becomes much harder as you typically do not have a user bound to the current request. In this scenario you will typically have the context-data available runtime, at the same location you want to verify if a toggle should be on or of.

It would thus be beneficial if the client also could inject context data at runtime. The unleash API would then be overloaded with a new method, the the user can inject the required context-data.

unleash.isEnabled("app.someToggle", UnleashConext.of("k1", "v1", "k2", "v2"));

Unleash will then in turn inject the context-data to the strategy which would be extended to:

public interface Strategy {
    String getName();

    boolean isEnabled(Map<String, String> parameters);

    boolean isEnabled(Map<String, String> parameters, UnleashConext context);
}

(* if we move the client to java8 we could also provide a default implementation of the new method to avoid breaking the API)

This is also the approach used for the unleash-client-node.

Problem with userWithId strategy

I'm putting this here even thought it relates to the Finn client.
There was additional whitespace after an id in the parameter so it didn't match.
We should maybe remove whitespace from the parameters?
This might effect more than just the userWithId strategy.

skjermbilde 2015-03-05 kl 17 07 03

Error-msg on first load on new node

If the tmp-backup-file is missing unleash-client fgenerates this warning:

[ WARN] 13:11:09 Unable to locate backup file:'/tmp/unleash-repo.json' []
java.io.FileNotFoundException: /tmp/unleash-repo.json (No such file or directory)
    at java.io.FileInputStream.open(Native Method) ~[?:1.8.0_05]
    at java.io.FileInputStream.<init>(FileInputStream.java:131) ~[?:1.8.0_05]
    at java.io.FileInputStream.<init>(FileInputStream.java:87) ~[?:1.8.0_05]
    at java.io.FileReader.<init>(FileReader.java:58) ~[?:1.8.0_05]
    at no.finn.unleash.repository.BackupFileHandler.read(BackupFileHandler.java:24) [unleash-client-java-1.0-SNAPSHOT.jar:?]
    at no.finn.unleash.repository.FeatureToggleRepository.<init>(FeatureToggleRepository.java:43) [unleash-client-java-1.0-SNAPSHOT.jar:?]
    at no.finn.unleash.repository.FeatureToggleRepository.<init>(FeatureToggleRepository.java:36) [unleash-client-java-1.0-SNAPSHOT.jar:?]
    at no.finn.unleash.DefaultUnleash.<init>(DefaultUnleash.java:20) [unleash-client-java-1.0-SNAPSHOT.jar:?]
    at no.finntech.talent.web.config.ApplicationConfig.unleash(ApplicationConfig.java:103) [classes/:?]
    at no.finntech.talent.web.config.ApplicationConfig$$EnhancerBySpringCGLIB$$c9f14094.CGLIB$unleash$6(<generated>) [spring-core-4.0.2.RELEASE.jar:?]
    at no.finntech.talent.web.config.ApplicationConfig$$EnhancerBySpringCGLIB$$c9f14094$$FastClassBySpringCGLIB$$5e09d657.invoke(<generated>) [spring-core-4.0.2.RELEASE.jar:?]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) [spring-core-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:312) [spring-context-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at no.finntech.talent.web.config.ApplicationConfig$$EnhancerBySpringCGLIB$$c9f14094.unleash(<generated>) [spring-core-4.0.2.RELEASE.jar:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_05]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_05]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_05]
    at java.lang.reflect.Method.invoke(Method.java:483) ~[?:1.8.0_05]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:166) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:580) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1094) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:989) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1014) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:957) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:855) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:480) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:700) [spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760) [spring-context-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482) [spring-context-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:658) [spring-webmvc-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:530) [spring-webmvc-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:484) [spring-webmvc-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:136) [spring-webmvc-4.0.2.RELEASE.jar:4.0.2.RELEASE]
    at javax.servlet.GenericServlet.init(GenericServlet.java:244) [javax.servlet-api-3.1.0.jar:3.1.0]
    at org.eclipse.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:561) [jetty-servlet-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.servlet.ServletHolder.initialize(ServletHolder.java:351) [jetty-servlet-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.servlet.ServletHandler.initialize(ServletHandler.java:840) [jetty-servlet-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:300) [jetty-servlet-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1347) [jetty-webapp-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:745) [jetty-server-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:492) [jetty-webapp-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:69) [jetty-util-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:117) [jetty-util-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.server.Server.start(Server.java:355) [jetty-server-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:99) [jetty-util-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:60) [jetty-server-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.server.Server.doStart(Server.java:324) [jetty-server-9.1.2.v20140210.jar:9.1.2.v20140210]
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:69) [jetty-util-9.1.2.v20140210.jar:9.1.2.v20140210]
    at no.finntech.talent.web.LocalJettyServer.start(LocalJettyServer.java:64) [test-classes/:?]
    at no.finntech.talent.web.LocalJettyServer.main(LocalJettyServer.java:28) [test-classes/:?]

getting error while using unleash client while running web app cygwin

2019-02-07 12:59:51.570  WARN 5092 --- [sh-api-executor] n.f.u.r.FeatureToggleRepository          : Could not refresh feature toggles
no.finn.unleash.UnleashException: Could not fetch toggles
        at no.finn.unleash.repository.HttpToggleFetcher.fetchToggles(HttpToggleFetcher.java:48)
        at no.finn.unleash.repository.FeatureToggleRepository.lambda$updateToggles$0(FeatureToggleRepository.java:43)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.net.ConnectException: Connection refused: connect
        at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
        at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
        at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
        at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
        at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
        at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
        at java.net.Socket.connect(Socket.java:589)
        at sun.net.NetworkClient.doConnect(NetworkClient.java:175)
        at sun.net.www.http.HttpClient.openServer(HttpClient.java:463)
        at sun.net.www.http.HttpClient.openServer(HttpClient.java:558)
        at sun.net.www.http.HttpClient.<init>(HttpClient.java:242)
        at sun.net.www.http.HttpClient.New(HttpClient.java:339)
        at sun.net.www.http.HttpClient.New(HttpClient.java:357)
        at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1220)
        at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1156)
        at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1050)
        at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:984)
        at no.finn.unleash.repository.HttpToggleFetcher.fetchToggles(HttpToggleFetcher.java:37)
        ... 8 common frames omitted

When we run web application in cygwin , has windows OS , its creating incorrect windows path with wrong slash using java client SDK

its throwing while creating repo file with /cygwin/ C/x/ff.json , this slash will not work in cygwin

Publish events

The client should provide a way for users to subscribe on interesting events. Preferably without introducing third party dependencies.

Examples:

  • ready - Unleash client has fetched initial state from unleash-server is able to serve request
  • update - Unleash client got updated toggle configuration from server.
  • error - Unleash client failed fetching data from unleash-server (might want to set some retry limit?).

Java client never fetches from server

I'm having trouble getting the Java client setup and running. Everything looks fine from the logs when running a simple test.

UnleashConfig config = UnleashConfig.builder()
				.appName("test")
				.instanceId("localhost")
				.unleashAPI("http://localhost:4242/api/")
				.build();
Unleash client = new DefaultUnleash(config);
assertTrue(client.isEnabled("test-feature"));

I'm running client v3.1.2 and server locally v3.1.7. I'm able to connect to the server fine using curl and also while running the node client. Both return expected results. The only logs I can see are

09:56:04.579 [main] INFO  no.finn.unleash.repository.ToggleBackupHandlerFile - Unleash will try to load feature toggle states from temporary backup
09:56:04.579 [main] INFO  no.finn.unleash.repository.ToggleBackupHandlerFile - Unleash will try to load feature toggle states from temporary backup
09:56:04.590 [main] WARN  no.finn.unleash.repository.ToggleBackupHandlerFile -  Unleash could not find the backup-file '/var/folders/nd/1c9btckx64jdxkj94j7dy8400000gn/T//unleash-test-repo.json'. 
This is expected behavior the first time unleash runs in a new environment.
09:56:04.590 [main] WARN  no.finn.unleash.repository.ToggleBackupHandlerFile -  Unleash could not find the backup-file '/var/folders/nd/1c9btckx64jdxkj94j7dy8400000gn/T//unleash-test-repo.json'. 
This is expected behavior the first time unleash runs in a new environment.

When I add a backup file and specify it in the builder then the last 2 log messages disappear however the result is still wrong. When I add the expected feature flag to the backup file then the above test passes so it seems like it's only fetching from the 'local backup' without ever going out to the server.

From the node client I get the following log on the server: New client registered with appName=ben-node-client and instanceId=ben-macbookpro which I never get from the Java client.

Any help would be greatly appreciated! I really like this project and would love to get it running.
Thanks

Unable to detect failures in HttpToggleFetcher

The current API of Unleash/HttpToggleFetcher makes it difficult to detect if HttpToggleFetcher is actually able to fetch toggles or if it experiences for example network failures, authentication/authorization failures or simply 404s because because somebody shut down or moved the unleash server.

It would be nice if applications are able to observe such problems through the API.

Critical bug: empty of bogus backup file will crash the client at startup.

How to reproduce:

  1. Make sure /tmp/unleash-repo.json is empty or invalid json.
  2. Run the UnleashUsageTest found the the test directory.

It will crash the unleash with an constructor excpetion (se example)

Suggested solution:
Add an extra catch in ToggleBackupHandlerFile.java to make sure we also handle bogus backup-file.

java.lang.IllegalStateException: Could not extract toggles from json
    at no.finn.unleash.repository.JsonToggleParser.fromJson(JsonToggleParser.java:23)
    at no.finn.unleash.repository.ToggleBackupHandlerFile.read(ToggleBackupHandlerFile.java:29)
    at no.finn.unleash.repository.FeatureToggleRepository.<init>(FeatureToggleRepository.java:42)
    at no.finn.unleash.repository.FeatureToggleRepository.<init>(FeatureToggleRepository.java:35)
    at no.finn.unleash.DefaultUnleash.<init>(DefaultUnleash.java:22)
    at no.finn.unleash.example.UnleashUsageTest.wire(UnleashUsageTest.java:17)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)

Support for expecific feature change subscriber

Context
Currently I have a system that need some heavy software pieces in the case of a feature is enable. You can image that when the feature is disable I don´t need for example connection to database or Kafka or whatever. Then I am creating a reactive system that create this pieces when the feature turn on and destroy it when turn off.

In order to do that I create a subscriber like this:

@Bean
fun unleash(): Unleash {
    val config = UnleashConfig.builder()
        .appName(application)
        .instanceId(getEnv("HOSTNAME"))
        .unleashAPI(url)
        .subscriber(object : UnleashSubscriber {

            override fun togglesFetched(toggleResponse: FeatureToggleResponse) {
                if (toggleResponse.status == CHANGED) {
                    //create heavy pieces
                }
            }
        })
        .build()
    return DefaultUnleash(config)
}

My problem
My first workaround was try to check the values that interest me form 'togglesFetched' via the
'toggleResponse: FeatureToggleResponse' paramether. The problem is that this class is internal class we can not access to anything.

My seconds workaround is to check the feature value via “isEnabled” or “getVariant”. I need the ‘Unleash’ object (aka the client) inside the subcriber, but I am creating the client.
Can you see the clicle?

Temporal solution
After a talk with @ivarconr we agree follow this approach: We could create your subscriber outside and provide the Unleash instance via a setter.

class MyUnleashSubscriber implements UnleashSubscriber {
    Unleash instance;
    
    public void setInstance(Unleash unleash) {
        instance = unleash;
    }

    @Override
    public void togglesFetched(FeatureToggleResponse toggleResponse) {
        if(toggleResponse.getStatus() == CHANGED) {
            if(instance != null) {
                instance.isEnabled("Demo");
            }
        }
       }
    }

    MyUnleashSubscriber subscriber = new MyUnleashSubscriber();

        UnleashConfig unleashConfig = new UnleashConfig.Builder()
                .appName("java-test")
                .instanceId("instance y")
                .unleashAPI("https://unleash.herokuapp.com/api/")
                .subscriber(subscriber)
                        .build())
                .build();

        Unleash unleash = new DefaultUnleash(unleashConfig);
        subscriber.setInstance(unleash);

Obviously this work but I think that we can improve the support of that situation by the client allowing add subscriber dynamically after the creation or exposing the features values in the subscriber (in a external format of course)

Plugin Api to registrer custom strategies

It would have been nice with the a possibility to add strategies at runtime. E.g. adding the following addStrategy method:

interface Unlesh{
boolean isEnabled(String toggleName);
boolean isEnabled(String toggleName, boolean defaultSetting);
addStrategy(Strategy strategy);
}

Strategy for strategies

Unleash has gotten some pull requests to the Finn client and it is good that people want to use the service. I just want to raise a concern.

Right now this version has the following strategies:

  • DefaultStrategy
  • UnknownStrategy

Finn client has the following strategies:

  • ActiveForUserWithIdStrategy
  • BetaUserStrategy
  • ByHostNameStrategy
  • GradualRolloutRandomStrategy
  • RemoteAddrStrategy

Should we not move more of the strategies from the Finn client over to the open source one?

I also suggest that we add a markdown file that describes the strategies with example use cases.

Improve error messages

When connecting to the unleash-server over http instead of https and the client only receives 301 it will only log 301 and not the new location header. This can make it hard for the use to understand why the client cannot connect to the server.

4XX errors does not log anything

The client should log if it gets 400 errors from the server.

  • How noisy should it be on this?
  • Should it mute after a few errors?
  • Should this option be configurable?

Setup release pipeline

Our current release unleash-java-client:0.1-alpha, i did manually on my computer. We should automate the release (with a manual trigger).

Need to decide if we'll setup Travis to do the release build, or maybe FINN.no's internal build system. The release process needs credientials for sonatype repo and a password to unluck a pgp key for signing the artifact. Is it possible have these credentials secured and used by Travis?

Local backup json file is not getting updated

Local backup json file is not getting updated when disabled labels in UI, disable /enable the lables in admin GUI , is not reflected in unleash client in multiple AWS ec2 instance, it only picks up the first time settings

MetricsBucket is not thread safe (NullpointerException)

We sometimes get this exception:

java.lang.NullPointerException: null
    at no.finn.unleash.metric.MetricsBucket.registerCount(MetricsBucket.java:19)
    at no.finn.unleash.metric.UnleashMetricServiceImpl.count(UnleashMetricServiceImpl.java:43)
    at no.finn.unleash.DefaultUnleash.count(DefaultUnleash.java:138)
    at no.finn.unleash.DefaultUnleash.isEnabled(DefaultUnleash.java:85)

The issue seems to be that MetricsBucket is not thread safe. Changing the code to use a ConcurrentHashMap (using ConcurrentHashMap.computeIfAbsent in MetricsBucket.getOrCreate) would likely fix the problem

feat: function for returning headers

This would allow for dynamic header setting, which is important to enable rotation of auth-tokens etc without restarting.

For reference, this feature is already implemented in the Node.js SDK, see Unleash/unleash-client-node#151

My thought was that it should be possible to register a function as part of the Unleash Configuration which would return a Map (name, value) of headers to add request sent to the unleash-server. If this function is set it should take precedence over customHttpHeaders already part of the UnleashConfig.java.

FeatureToggleRepository has set intialDelay to pooling interval

Now we have to wait a full polling interval before the client syncs feature toggle states with the server on start-up.

This can be significantly improved by just setting the initial delay to 0, forcing the client to perfom a server sync immediately.

Change strategy interface

There have been one request to change the strategy interface and have unleash also inject which feature toggle is being checked now.

The reasoning behind it is to easier support gradual rollout in a such way that one can use the hashcode of the user in combination with the feature toggle. This will allow gradual rollout to be consistent within an toggle (eg. same users will fall within 1% and 5%) but not across different feature toggles. I see no reason to hide this information from the strategy-implementation, and most strategies can just ignore this extra parameter.

The new interface will the be:

public interface Strategy {
    String getName();

    boolean isEnabled(String toggleName, Map<String, String> parameters);
}

This will however be a breaking change, but most of our users have not yet started implementing their own strategies.

The same behavior can be achieved by having an extra config parameter in the strategy-parameters, but is more burdensome to the user.

Any thoughts?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.