Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic Config Resolver #459

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

andrew4699
Copy link
Contributor

@andrew4699 andrew4699 commented Nov 20, 2024

Description

Introduces the notion of a DynamicFeatureConfigResolver, which can be used to dynamically resolve featureConfiguration values at runtime. This is intended for integration with feature flag systems (Statsig, LaunchDarkly, etc) but this PR does not provide the implementations, rather just a hook to make it possible.

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

  • Unit tests
  • Tested end-to-end with a sample implementation

Checklist:

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • If adding new functionality, I have discussed my implementation with the community using the linked GitHub issue


@ParameterizedTest
@MethodSource("getTestConfigs")
public void testPrecedenceIsDynamicThenStaticPerRealmThenStaticGlobal(TestConfig testConfig) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about catalog-level? I'd assume the catalog-level properties take precedence over everything, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on this it already should. I assumed there were tests already for that.

* integration with feature flag systems which are intended for fetching configs at runtime.
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
public interface DynamicFeatureConfigResolver extends Discoverable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, I feel that this type should be unnecessary. Unfortunately, we just added the featureConfiguration field as a map, rather than having the DefaultConfigurationStore instantiated and populated by Jackson during application startup. Now the configuration can't really change to add, e.g., a type configuration key so that we could use different PolarisConfigurationStore implementations. The PolarisConfigurationStore interface was always intended to support dynamic implementations - which is why the PolarisCallContext is part of the signature.

I'm just trying to figure out if there isn't a way to make the implementation swappable, while still supporting the backward-compatible configuration. I mean, there's no real harm in adding another interface, but it just feels unnecessary and the consequence of poor decision making earlier on. I hate the idea of that poor decision making having to follow us around forever.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to make the whole implementation swappable as you mentioned, which could be implemented in a backward compatible way: if a custom type is specified, use it, otherwise use the default. I don't think this change makes that impossible in the future. In fact, you can simply make DynamicFeatureConfigResolver implement the entire configuration store.

@snazy
Copy link
Member

snazy commented Nov 22, 2024

What are the things that need / depend on this change?

@andrew4699
Copy link
Contributor Author

What are the things that need / depend on this change?

Hey @snazy I'm not sure I understand the intent of the question. This is the only change I have planned regarding dynamic configs, as this provides the hook necessary for implementors.

@snazy
Copy link
Member

snazy commented Nov 25, 2024

Hey @snazy I'm not sure I understand the intent of the question. This is the only change I have planned regarding dynamic configs, as this provides the hook necessary for implementors.

My question is: what/who needs this change?

My overall concern is it's going beyond a "simple" implementation. Maybe I'm a bit blessed as I'm used to what Quarkus and all its extensions offer for configuration via Smallrye-Config, where such things (various config sources, dynamic configurations, encryption, etc etc) are rather just available.

@andrew4699
Copy link
Contributor Author

@snazy Snowflake is interested in the capability. This is pretty much the simplest and most flexible implementation possible. It can exist regardless of the framework as the implementation can fetch config from anywhere. Can you share an example of how you could inject dynamic, account-level config from a 3rd party in the Quarkus PR?

@snazy
Copy link
Member

snazy commented Nov 25, 2024

The example is pretty easy:

@Inject MyConfig myConfig;

Where MyConfig would be sth like

public interface MyConfig {
   public int someIntValue();
   public OtherThing other();
   public Duration someDuration();
}

The rest is just smallrye-config setup and usage stuff - some starter information is here. Config sources do not have to be static - those can provide dynamic values - but that's up to the config source really.

I'd really prefer an approach that leverages functionality that is proven to work in the wild for quite some time now, and with Quarkus there are already a lot of extensions that do provide "dynamic configuration" - for example this one or this one. Using already existing functionality is IMHO more efficient, think of #469, than writing some custom configuration framework.

@jbonofre
Copy link
Member

NB: you can take a look on #469 where we are using injected confict.

Especially: https://github.com/jbonofre/polaris/blob/QUARKUS/polaris-service-quarkus/README.md#configuration

@collado-mike
Copy link
Contributor

Another option would be to make the whole implementation swappable as you mentioned, which could be implemented in a backward compatible way: if a custom type is specified, use it, otherwise use the default.

I think this is a reasonable approach and I think this should address the concern of where we get the dynamic config. A Quarkus impl can use whatever the smallrye config structure internally, but we can't tie it to that. The PolarisConfigurationStore is intended to support feature rollouts and feature configuraiton globally, but also to be fine-tuned at the realm-level or catalog level. Ultimately, I think it's going to need to allow for namespace and table-level configuration as well. Configuration should be inheritable, so it'll have to have the concept of table-lineage (i.e., table inherits from namspace inherits from catalog inherits from service) and we're just not going to see that in a generalized configuration framework. I'm all for not reinventing the wheel, but we shouldn't hand-wave over the details and pretend it all looks like a configuration file parser.

@adutra
Copy link
Contributor

adutra commented Nov 26, 2024

Another option would be to make the whole implementation swappable as you mentioned, which could be implemented in a backward compatible way: if a custom type is specified, use it, otherwise use the default.

I also would prefer this approach, as it is CDI-friendly.

But I also think that, before diving head first on making things "dynamic", we should take some time to design a revamped PolarisConfigurationStore SPI that would address all the requirements. I've seen the below requirements already voiced (there may be others):

  • Ability to (optionally?) hot-reload.
  • Ability to merge layers of configuration: application > realm > namespace > table.
  • Ability to swap implementations (mostly to fetch configuration from different sources).

But the current PolarisConfigurationStore API does not surface any of that. E.g. there is no hotReload() method; there is no merge() method. Moreover, none of the methods take a realm, or a table as argument – instead, the realm is "implicitly passed" through a ThreadLocal in CallContext, and becomes an implementation detail. This makes me think that PolarisConfigurationStore needs a complete redesign.

On top of that, I always wondered why PolarisConfiguration has such a strange shape. Instead of a classic config bean:

interface PolarisConfiguration {
  boolean configX();
  Optional<String> configY();
  List<String> configZ();
}

... that each implementor would be free to implement their own way, we have:

public class PolarisConfiguration<T> {
  public static final PolarisConfiguration<Boolean> CONFIG_X = ...
  public static final PolarisConfiguration<String> CONFIG_Y = ...
  public static final PolarisConfiguration<List<String>> CONFIG_Z = ...
}

... which is a lot harder to extend, or to make "dynamic" or "layered". And involves many runtime casts that are error-prone, cf PolarisConfigurationStore#tryCast(). So, again, I think PolarisConfiguration also needs a complete redesign.

I would therefore suggest that we embrace the big picture and redesign PolarisConfigurationStore and PolarisConfiguration with all the above considerations and requirements in mind, before adding more features such as "dynamic resolution".

Copy link

This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions bot added the Stale label Dec 27, 2024
@adutra
Copy link
Contributor

adutra commented Dec 31, 2024

FYI, Quarkus has recently introduced a new extension that integrates Quarkus with Open Feature, a backend-agnostic feature flag library. Open Feature has support for Statsig, Unleash, flagd and a few others ( but not LaunchDarkly apparently EDIT: they do have support for LaunchDarkly).

This is all bleeding edge for sure, but we may want to explore this approach as well, as opposed to implementing our own from scratch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants