Coder Social home page Coder Social logo

quarkus-unleash's Introduction

Quarkus Unleash

Build License Central

All Contributors

Compatibility

Quarkus Unleash provides two different version streams, one compatible with Quarkus 2.x and the other compatible with Quarkus 3.x.

Quarkus Quarkus Unleash Documentation
2.x 0.x Documentation
3.x 1.x Documentation

Use the latest version of the corresponding stream, the list of versions is available on Maven Central.

Contributors โœจ

Thanks goes to these wonderful people (emoji key):

Andrej Petras
Andrej Petras

๐Ÿ’ป ๐Ÿšง
Melloware
Melloware

๐Ÿ“–
Gwenneg Lepage
Gwenneg Lepage

๐Ÿ’ป ๐Ÿšง ๐Ÿ“–

This project follows the all-contributors specification. Contributions of any kind welcome!

quarkus-unleash's People

Contributors

actions-user avatar allcontributors[bot] avatar andrejpetras avatar dependabot[bot] avatar dorianm avatar gastaldi avatar gsmet avatar gwenneg avatar maxandersen avatar melloware avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

quarkus-unleash's Issues

Empty `@FeatureVariant#name()` and `@FeatureToggle#name()` could be checked at build time

Not a big deal, but @FeatureVariant#name() and @FeatureToggle#name() are currently validated at run time while this could be done at build time:

if (ft == null || ft.name().isEmpty()) {
throw new IllegalStateException("No feature toggle name of the variant specified");
}

if (ft == null || ft.name().isEmpty()) {
throw new IllegalStateException("No feature toggle name specified");
}

Unleash 0.2.0 : isEnable for userId strategy does not work anymore

Describe the bug

I am trying to call method :
isEnabled(String toggleName, UnleashContext context, boolean defaultSetting)

New UnleashService does not override all the default method of Unleash class and thus the context is not propagated to the client.

Unleash interface:
default boolean isEnabled(String toggleName, UnleashContext context, boolean defaultSetting) { return this.isEnabled(toggleName, defaultSetting); }
This should be added to UnleashService:
@Override public boolean isEnabled(String toggleName, UnleashContext context, boolean defaultSetting) { return client.isEnabled(toggleName, context, defaultSetting); }

Expected behavior

Call unleash with userId strategy working.

Actual behavior

No response

How to Reproduce?

Reproducer:

  1. Create a feature flag with userIds strategy in unleash and add some user
  2. Call isEnable for this feature flag with user
    unleash.isEnabled("quarkus.unleash.test.with.userid", UnleashContext.builder().userId("test").build(), false);
  3. Will return false

Output of uname -a or ver

No response

Output of java -version

No response

GraalVM version (if different from Java)

No response

Quarkus version or git rev

No response

Build tool (ie. output of mvnw --version or gradlew --version)

No response

Additional information

No response

Injecting `@FeatureToggle Instance<Boolean>` vs `@FeatureToggle boolean`

The extension doc shows a code snippet which injects an Instance<Boolean> field without explaining why it should be preferred over a simple boolean. I haven't confirmed that yet but I suppose injecting a boolean field doesn't really work because the injected value won't reflect changes to the toggle made in Unleash. By injecting an Instance<Boolean>, each call to Instance#get will return the latest toggle value from Unleash. This is only an asumption so far, I should be able to test it tomorrow and confirm whether it's correct or not.

If it is, I think the doc and javadoc should provide details about the difference between @FeatureToggle Instance<Boolean> and @FeatureToggle boolean.

Quarkus in dev mode makes Unleash client crash after reload

Running Quarkus in dev mode makes Unleash client crash with following error after reload :

2023-01-23 18:01:25,510 ERROR [io.get.uti.UnleashScheduledExecutorImpl] (Quarkus Main Thread) Unleash background task crashed [Error Occurred After Shutdown]: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask@3c04bd52[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@3feb13e1[Wrapped task = io.getunleash.repository.FeatureRepository$$Lambda$1115/0x00000008011f1fd8@853b940]] rejected from java.util.concurrent.ScheduledThreadPoolExecutor@7315d9c7[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 4]

How to reproduce:

  1. Create a Quarkus app and add Unleash using quarkus-unleash as described in Readme.
  2. Get the app up and running, make sure that the Unleash toggles are read and working. Also, make sure that there are no errors in the logs.
  3. Make changes in the app code, hit save.
  4. Access the same endpoint using the Unleash toggles. Now you should see the similar stack trace in the logs.

Invalid quarkus.unleash.url generated for shared Dev Services container scenario

If the Unleash devservice is started after the same devservice has been initiated in another Quarkus app, the UnleashDevServiceProcessor generates a different quarkus.unleash.url compared to when an existing Unleash container is not found.

When the container is actually started by the processor, the URL built below is passed to the config map:

public String getUrl() {
    return String.format("http://%s:%d/api", this.getUnleashHost(), this.getPort());
}

However, when an existing container is found, the container address in the form localhost:port is passed to the config map:

return maybeContainerAddress
        .map(containerAddress -> new UnleashRunningDevService(FEATURE_NAME,
                containerAddress.getId(),
                null, configMap(containerAddress.getUrl(), null)))
        .orElseGet(defaultUnleashSupplier);

The latter causes the Unleash API is not a valid URL: localhost:4242 error because "localhost" is not a valid protocol when creating client URLs.

A workaround is to explicitly configure the quarkus.unleash.url for the shared container scenario, but this involves changing the configuration each time the startup order of the Quarkus apps changes.

For comparison, the Keycloak devservice allows for configuring the two applications so that when container sharing is active, no additional changes are needed in either application regardless of the startup order.

Support the Subscriber API

https://docs.getunleash.io/reference/sdks/java#subscriber-api

I have yet to confirm that, but it looks like the Subscriber API from Unleash could be used to listen to changes that happened to toggles.

I have two use cases where this could be helpful:

  • I need to log whenever a toggle is enabled/disabled
  • I need to detect changes made to a specific variant payload without relying on a scheduled job to retrieve the latest payload

Use an executor from Quarkus rather than the default one from the Unleash client

The Unleash client is currently initialized without passing an executor to the builder:

UnleashConfig.Builder builder = UnleashConfig.builder()
.unleashAPI(unleashRuntimeTimeConfig.url)
.appName(name);

By default, Unleash will rely on a default static executor:

public static synchronized UnleashScheduledExecutorImpl getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new UnleashScheduledExecutorImpl();
    }
    return INSTANCE;
}

We should probably replace it with an executor managed by Quarkus, if possible.

The 'name' attribute of @FeatureVariant is confusing

In Unleash, both toggles and variants have a name.

Toggle [name="foo", ...]
    \__ Variant [name="alpha", ...]
    \__ Variant [name="bravo", ...]
Toggle [name="bar", ...]

The @FeatureVariant annotation from quarkus-unleash, which is meant to retrieve the variant from a given toggle, requires a name attribute. Unless I missed something, there is currently no indication that the annotation requires the toggle name and not the variant name. If the variant name is provided (which is how I tried to use it initially), Unleash won't return any data.

My suggestion would be to introduce a new FeatureVariant#toggleName attribute, deprecate FeatureVariant#name immediately and remove it eventually. The annotation could also be better documented with Javadoc and the extension doc.

Finally, for the sake of consistency if that is implemented, FeatureToggle#toggleName could be considered as well to replace FeatureToggle#name.

Disable fallback to backup file containing last fetched feature flags

I have encountered the problem, that when the my service is trying to fetch information about the feature flags without having internet connection (potentially can be other cases), your implementation fallbacks to the backup file. Here is the info message:
[io.get.rep.FeatureBackupHandlerFile] (Quarkus Main Thread) Unleash will try to load feature toggle states from temporary backup.

I would like to disable the fallback and get null or an exception if the HTTP request fails. Currently I cannot find a proper way to do that without modifying your implementation, which I want to avoid doing.

Is there a way to disable the fallback to backup file? If not, is there indirect way to do so? For example, check if connection to the API endpoint has failed. You are doing it in HttpFeatureFetcher class by reacting to the exception thrown from HttpURLConnection as following:

 @Override
    public ClientFeaturesResponse fetchFeatures() throws UnleashException {
        HttpURLConnection connection = null;
        try {
            connection = openConnection(this.toggleUrl);
            connection.connect();

            return getFeatureResponse(connection, true);
        } catch (IOException e) {
            throw new UnleashException("Could not fetch toggles", e);
        } catch (IllegalStateException e) {
            throw new UnleashException(e.getMessage(), e);
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

I tried to use the similar approach, but to properly do it I need access to UnleashConfig at runtime, which I also cannot find a way to access. Currently I can only perform the check as following:

try {
	URL url = new URL(unleashUrl.orElse(""));
	HttpURLConnection connection = (HttpURLConnection) url.openConnection();
	connection.connect();
	
	int responseCode = connection.getResponseCode();
	
	if(!(responseCode == HttpURLConnection.HTTP_UNAUTHORIZED)){
		throw new Exception();
	}
	
	return true;
} catch (Exception e) {
	logger.error("Error connecting to the API: " + e.getMessage());
	return false;
}

But since I do not provide all the information as you do, the response from connection.connect() in my approach either ends up in catch block as there is no internet connection or returns 401 (not authorized). This way I capture the cases when there is no internet connection or API endpoint unavailable. However, everything that can happen after authorization is not taken into account. Configuring the authorized request manually (basically repeating your code) to have more control does not seem feasible.

Crash on feature toggle fetch in native image

Hi,

the integration of the quarkus-unleash extension is fine in a JVM-based context, but once we deployed the application as a native image with Graalvm, the following crash happened on startup:

java.lang.RuntimeException: Failed to start quarkus
	at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
	at io.quarkus.runtime.Application.start(Application.java:101)
	at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:111)
	at io.quarkus.runtime.Quarkus.run(Quarkus.java:71)
	at io.quarkus.runtime.Quarkus.run(Quarkus.java:44)
	at io.quarkus.runtime.Quarkus.run(Quarkus.java:124)
	at io.quarkus.runner.GeneratedMain.main(Unknown Source)
Caused by: java.util.ServiceConfigurationError: java.net.ContentHandlerFactory: Provider sun.awt.www.content.MultimediaContentHandlers not found
	at [email protected]/java.util.ServiceLoader.fail(ServiceLoader.java:593)
	at [email protected]/java.util.ServiceLoader.loadProvider(ServiceLoader.java:875)
	at [email protected]/java.util.ServiceLoader$ModuleServicesLookupIterator.hasNext(ServiceLoader.java:1084)
	at [email protected]/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1309)
	at [email protected]/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1393)
	at [email protected]/java.net.URLConnection$2.run(URLConnection.java:1401)
	at [email protected]/java.net.URLConnection$2.run(URLConnection.java:1391)
	at [email protected]/java.security.AccessController.executePrivileged(AccessController.java:171)
	at [email protected]/java.security.AccessController.doPrivileged(AccessController.java:318)
	at [email protected]/java.net.URLConnection.lookupContentHandlerViaProvider(URLConnection.java:1390)
	at [email protected]/java.net.URLConnection.getContentHandler(URLConnection.java:1303)
	at [email protected]/java.net.URLConnection.getContent(URLConnection.java:760)
	at [email protected]/sun.net.www.protocol.https.HttpsURLConnectionImpl.getContent(HttpsURLConnectionImpl.java:404)
	at io.getunleash.repository.HttpFeatureFetcher.getFeatureResponse(HttpFeatureFetcher.java:60)
	at io.getunleash.repository.HttpFeatureFetcher.fetchFeatures(HttpFeatureFetcher.java:38)
	at io.getunleash.repository.FeatureRepository.lambda$updateFeatures$0(FeatureRepository.java:96)
	at io.getunleash.repository.FeatureRepository.initCollections(FeatureRepository.java:80)
	at io.getunleash.repository.FeatureRepository.<init>(FeatureRepository.java:40)
	at io.getunleash.repository.FeatureRepository.<init>(FeatureRepository.java:28)
	at io.getunleash.DefaultUnleash.defaultToggleRepository(DefaultUnleash.java:50)
	at io.getunleash.DefaultUnleash.<init>(DefaultUnleash.java:54)
	at io.quarkiverse.unleash.runtime.UnleashCreator.createUnleash(UnleashCreator.java:31)
	at io.quarkiverse.unleash.runtime.UnleashService.initialize(UnleashService.java:30)
	at io.quarkiverse.unleash.runtime.UnleashService_ClientProxy.initialize(Unknown Source)
	at io.quarkiverse.unleash.runtime.UnleashRecorder.initializeProducers(UnleashRecorder.java:12)
	at io.quarkus.deployment.steps.UnleashProcessor$configureRuntimeProperties116716570.deploy_0(Unknown Source)
	at io.quarkus.deployment.steps.UnleashProcessor$configureRuntimeProperties116716570.deploy(Unknown Source)
	... 7 more

Version info:

  • Quarkus version 3.2.4.Final
  • base image: mandrel:23.0.1.2.83-Final-java17-amd64
  • io.quarkiverse.unleash:quarkus-unleash:1.0.1
  • io.getunleash:unleash-client-java:8.0.0

Is this something which should be addressed in the reflection config?

Thanks for your help :)

Producer classes should not have a CDI scope unless there's a good reason for it

The following classes currently have a @Singleton CDI scope:

  • FeatureToggleProducer
  • ToggleVariantProducer
  • ToggleVariantStringProducer
  • ToggleVariantObjectProducer
  • UnleashResourceProducer

After #227 is merged and the Unleash scope changes from @Dependent to @ApplicationScoped, the @Singleton scope should be removed from all classes listed above.

Disable the extension from the runtime configuration

Hey everyone! Thanks for creating this extension, it's great!

However, I'd like the ability to disable it from the runtime configuration and I didn't find a way to do that. It could be done with a simple quarkus.unleash.enabled=true|false config key.

Does that sound like a reasonable enhancement request? If so, I might try to implement that myself, but I don't have a lot of time available so if anyone has free cycles before I do, please feel free to do it ๐Ÿ˜„

Multiple clients registerd in dev mode

Using dev mode it looks like there are multiple clients instantiated. A new one is added on every recompilation of the code:

12:39:58,311 ERROR traceId=, parentId=, spanId=, sampled= [io.get.DefaultUnleash] (Quarkus Main Thread) You already have 21 clients for AppName [dev] with instanceId: [REDACTED] running. Please double check your code where you are instantiating the Unleash SDK mdc:[{}]

This means that I get 21 update for changes for feature flags in dev mode.

Maybe related to #228.

Selecting a bean at runtime depending on whether feature flag is enabled

Currently I have two beans that implement the same interface, and the decision on which to inject uses the annotation @LookupIfProperty. And I control this property using environment variables.

However, I'd like to replace this with Unleash. Is there any way I can get similar behavior?

Or do I have to use Instance.select() ?

Memory leak risk with `@FeatureToggle Instance<Boolean>` and `@FeatureVariant Instance<?>`

From what I saw in the extension code, when @FeatureToggle Instance<Boolean> or @FeatureVariant Instance<?> are used, the injected bean doesn't have an explicit CDI scope which means it defaults to @Dependent.

When a @Dependent bean is injected with Instance<?> into an @ApplicationScoped or @Singleton bean and is no destroyed when it's no longer needed, it stays in the memory forever, so there's basically a memory leak.

Is there a risk of memory leak with this extension, or did I miss something? If there is a risk of leak, the doc should at least explain how to prevent it and show how the injected bean can be destroyed.

README.MD Add Configuration Docs

Great job on this extension. However I had to look at your source code for information on the Configuration options it would be nice if these were right inside the README.MD. I can submit a PR if interested?

native build

The extension doesn't works in native mode.
Quarkus can't start:

__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2021-02-24 19:36:39,215 INFO  [no.fin.unl.rep.ToggleBackupHandlerFile] (main) Unleash will try to load feature toggle states from temporary backup
2021-02-24 19:36:39,216 ERROR [io.qua.run.Application] (main) Failed to start application (with profile prod): java.lang.NullPointerException
        at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1011)
        at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006)
        at no.finn.unleash.repository.ToggleCollection.<init>(ToggleCollection.java:19)
        at no.finn.unleash.repository.JsonToggleCollectionDeserializer.deserializeVersion1(JsonToggleCollectionDeserializer.java:67)
        at no.finn.unleash.repository.JsonToggleCollectionDeserializer.deserialize(JsonToggleCollectionDeserializer.java:31)
        at no.finn.unleash.repository.JsonToggleCollectionDeserializer.deserialize(JsonToggleCollectionDeserializer.java:14)
        at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:69)
        at com.google.gson.Gson.fromJson(Gson.java:932)
        at com.google.gson.Gson.fromJson(Gson.java:870)
        at no.finn.unleash.repository.JsonToggleParser.fromJson(JsonToggleParser.java:23)
        at no.finn.unleash.repository.ToggleBackupHandlerFile.read(ToggleBackupHandlerFile.java:33)
        at no.finn.unleash.repository.FeatureToggleRepository.<init>(FeatureToggleRepository.java:46)
        at no.finn.unleash.repository.FeatureToggleRepository.<init>(FeatureToggleRepository.java:26)
        at no.finn.unleash.DefaultUnleash.defaultToggleRepository(DefaultUnleash.java:45)
        at no.finn.unleash.DefaultUnleash.<init>(DefaultUnleash.java:53)
        at io.quarkiverse.unleash.runtime.UnleashCreator.createUnleash(UnleashCreator.java:37)
        at io.quarkiverse.unleash.runtime.UnleashProducer.initialize(UnleashProducer.java:16)
        at io.quarkiverse.unleash.runtime.UnleashProducer_ClientProxy.initialize(UnleashProducer_ClientProxy.zig:215)
        at io.quarkiverse.unleash.runtime.UnleashRecorder.initializeProducers(UnleashRecorder.java:11)
        at io.quarkus.deployment.steps.UnleashProcessor$configureRuntimeProperties186348976.deploy_0(UnleashProcessor$configureRuntimeProperties186348976.zig:87)
        at io.quarkus.deployment.steps.UnleashProcessor$configureRuntimeProperties186348976.deploy(UnleashProcessor$configureRuntimeProperties186348976.zig:40)
        at io.quarkus.runner.ApplicationImpl.doStart(ApplicationImpl.zig:620)
        at io.quarkus.runtime.Application.start(Application.java:90)
        at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:100)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:66)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:42)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:119)
        at io.quarkus.runner.GeneratedMain.main(GeneratedMain.zig:29)

@FeatureVariant Instance<Variant> does not work on a constructor argument

This:

@ApplicationScoped
public class Bean {
    public Bean(@FeatureVariant(name = "test") Instance<Variant> variant) {
    }
}

leads to that:

java.lang.IllegalArgumentException: Not a field
        at org.jboss.jandex.MethodParameterInfo.asField(MethodParameterInfo.java:350)
        at io.quarkiverse.unleash.UnleashProcessor.generateProducer(UnleashProcessor.java:197)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:849)
        at io.quarkus.builder.BuildContext.run(BuildContext.java:256)
        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1512)
        at java.base/java.lang.Thread.run(Thread.java:833)
        at org.jboss.threads.JBossThread.run(JBossThread.java:501)

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.