-
Notifications
You must be signed in to change notification settings - Fork 116
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
Ability for ConfigSourceProvider to use Config #470
Comments
At today's hangout with @kenfinnigan @carlosdlr , below is the user case. ordinal= 700 |
I like the concept as we do currently have the issue of how you configure a ConfigSource. However how this works needs careful specification. Does this work for ConfigSources packaged in the same application. Do they need sorting by ordinal before intialising? |
@smillidge Initial thought is it would require a two-phase creation of Thus enabling higher ordinals to use values in the lower ones to assist in construction |
Small update, we're going to explore implementing this in SmallRye Config |
There seems to be some confusion here - the OP mentions lower to higher ordinal access, whilst the use case refers to the opposite (correct me if I'm wrong). Actually, both cases are relevant. Here's a real-world example (this is what we'd love to have in Keycloak.X): env variable (ordinal=300):
The user might provide just |
I like the idea of allowing a config source to specify the set of property keys that it depends on. I think the common use case here is the idea of using the config API itself to bootstrap other config sources. If a source implementation can declare the bootstrap properties it requires, then the loader can defer instantiation until it can satisfy the requirements. If it cannot reach a state where the requirements are met, then the source will never be instantiated. This has the pleasant side effect of being able to conditionally instantiate a config source. We could accomplish this in a backwards compatible manner by allowing a source to declare a constructor that takes a Config instance as a sole argument with an optional accompanying annotation to define the bootstrap requirements. Then the loader can pass an intermediate view of the config, backed by the already instantiated sources, to the source constructor. The loader must instantiate all the sources that do not declare bootstrap requirements first, so that the ones that do can still take advantage of the natural ordinal behavior (e.g. if the same property is available from environment variables and system properties we don't want to hand out the config as a bootstrap config until we've instantiated both sources). After instantiating all the "normal" sources, the loader should iterate over any discovered sources that have bootstrap requirements and attempt to instantiate them with the current config if the bootstrap requirements are satisfied. This process should repeat until no more sources are left to instantiate or the bootstrap requirements for all any remaining sources cannot be satisfied. Ordinal can't be taken into account until a source is instantiated, so we should be clear in specification that there can be no guarantee about the order of instantiation and the availability of other sources that have bootstrap requirements at bootstrap time. We could certainly invent a way for sources to indicate what properties they can provide at bootstrap time in order to make a more intelligent ordering decision in the loader, but I think that is a rabbit hole best avoided. Proposed Annotation: public @interface BootstrapConfig {
String[] requires() default {};
String[] requiresOne() default {};
} So, the most basic use case would be a config source that just wants to take advantage of any "normal" sources: public class MyConfigSource implements ConfigSource {
public MyConfigSource(Config config) {
// initialize source
}
} Which is functionally equivalent to: public class MyConfigSource implements ConfigSource {
public MyConfigSource(@BootstrapConfig Config config) {
// initialize source
}
} A more practical example would be a source that needs a particular property defined: public class MyConfigSource implements ConfigSource {
public MyConfigSource(@BootstrapConfig(requires = "my.config.boostrap") Config config) {
// initialize source
}
} Another example where a source can be configured in two different ways public class MyConfigSource implements ConfigSource {
public MyConfigSource(@BootstrapConfig(requiresOne = { "my.config.option1", "my.config.option2" }) Config config) {
// initialize source
}
} |
Interesting idea. I like the different method of determining the load order, my concern is it results in a lot of repetition over the |
I think in practice it would almost always boil down to exactly two iterations over a very small list. I imagine that sources requiring bootstrap config will prefer to rely on config coming from sources which do not have bootstrap requirements. So, their requirements will be satisfied on the first pass and the second pass will simply confirm that no more instantiations can be done (more likely the set is simply empty on the second pass attempt). Even in a pathological case with an extreme number of sources having interdependent bootstrap requirements, the cost of successive iterations should be quite small because the logic is trivial. Any significant time would be spent by the sources actually providing the required properties and that cost is unavoidable. Either way, it should be easy to test. Any platform config that uses this functionality should obviously be designed to be as efficient as possible. Application developers don't assume much greater risk than they already have with poorly implemented config sources, and they get a more powerful set of options in the bargain. |
I realized today that my proposal above is flawed due to the use of the service loader mechanism to discover config sources. This means that config source must have a no-args constructor and that is the only one that ever gets invoked by the service loader. Thus, the whole idea of having a special constructor doesn't work. With the service loader mechanism in mind, it seems to me that introducing a new factory type might be the way to go instead: public interface ConfigSourceFactory {
Optional<ConfigSource> getConfigSource(Config bootstrapConfig);
} Implementations would be discovered after the current config building process and then the Config bootstrapConfig; // start with a view of the config loaded thus far
List<ConfigSourceFactory> factories = new LinkedList<>();
ServiceLoader.load(ConfigSourceFactory.class)
.forEach(factories::add);
Iterator<ConfigSourceFactory> iterator;
Optional<ConfigSource> source;
for (boolean newSource = true; newSource && factories.size() > 0; ) {
newSource = false; // don't repeat unless we get another source on this pass
iterator = factories.iterator();
while (iterator.hasNext())
{
source = iterator.next().getConfigSource(bootstrapConfig);
if (source.isPresent())
{
source.get(); // add this source to bootstrapConfig
iterator.remove(); // discard this factory
newSource = true; // ensure we repeat for any remaining factories
}
}
} In this manner, the factory implementation is free to use whatever mechanism it wants to determine whether or not it can successfully create a source and the loading mechanism doesn't need any special handling to order instantiations. |
I think I think it's a bit more complicated because there are pure |
The problem with the existing mechanisms - and the genesis of this issue - is the lack of a standard way to access the config provided by already-registered sources. Introducing a third mechanism means we can keep the existing ones completely intact and still provide the desired functionality. Also, I think |
Is that purely for a "backwards compatible" position? In June we have a breaking change MP platform release, so now's the time to actually break things if we need to |
I suppose it's a combination of backwards-compatibility and pragmatism 😛 All things being equal, I would prefer a plan that reduces the surface area of of the SPI rather than expands it. The issues I've run into personally are the inability to conditionally provide a single source (although I see now how I could do this with Also, I'd love to see something before June... |
Just for posterity, I'd like to point out that it is possible to have a configuration source which is itself configured by other properties today (it's just not quite as easy as possible). Here's an example (feel free to cut & paste if desired): public class MyConfigSource implements ConfigSource {
enum State {
NOT_INITIALIZED,
INITIALIZING,
INITIALIZED,
}
private volatile State state = State.NOT_INITIALIZED;
private boolean ensureInitialized() {
if (state != INITIALIZED) {
synchronized (this) {
if (state == NOT_INITIALIZED) {
state = INITIALIZING;
// ... do the work of initial setup here ...
// ... this may include recursively reading config properties ...
state = INITIALIZED;
return true;
} else if (state == INITIALIZING) {
// recursive
return false;
} else {
assert state == INITIALIZED;
return true;
}
}
} else {
return true;
}
}
@Override
public String getValue(String key) {
if (! ensureInitialized()) {
return null;
}
// .. get the value ..
}
@Override
public Set<String> getPropertyNames() {
if (! ensureInitialized()) {
return Collections.emptySet();
}
// return our loaded property names
}
// etc.
} This results in a 0% API surface area increase. |
One problem with the lazy-initialization approach is that you may not be able to successfully initialize, but your source will be enlisted in the config anyway. Probably better to use a provider: public class MyConfigSourceProvider implements ConfigSourceProvider {
public Iterable<ConfigSource> getConfigSources(ClassLoader forClassLoader) {
MyConfigSource source = null;
// ... initialize source, if possible ...
return source == null ? Collections.emptySet() : Collections.singleton(source);
}
} Either way, you still have the problem of actually using the config API to get your properties. I suppose you can do something like this: ConfigProviderResolver configProviderResolver = ConfigProviderResolver.instance();
Config config = configProviderResolver.getBuilder()
.addDefaultSources()
.build();
try {
// ... use the config in some way ...
} finally {
configProviderResolver.releaseConfig(config);
} Then you can at least have access to the built-in sources, albeit at the cost of creating a separate config, but if you want to use |
If inability to initialize is a possibility, you could choose to throw exceptions or add a "failed" state which acts the same as Using the config API isn't a problem because recursion is handled by the state machine (synchronization is thread local), so you can just call |
@dmlloyd I fail to see how you can safely use The following implementation (along with the necessary import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.spi.ConfigSource;
public class MyConfigSource implements ConfigSource {
private static enum State {
NOT_INITIALIZED,
INITIALIZING,
INITIALIZED
}
private final Map<String, String> properties = new HashMap<String, String>();
private volatile State state = State.NOT_INITIALIZED;
private boolean ensureInitialized() {
if (state != State.INITIALIZED) {
synchronized (this) {
if (state == State.NOT_INITIALIZED) {
state = State.INITIALIZING;
ConfigProvider.getConfig();
state = State.INITIALIZED;
return true;
} else if (state == State.INITIALIZING) {
return false;
} else {
assert state == State.INITIALIZED;
return true;
}
}
} else {
return true;
}
}
public String getValue(String key) {
if (! ensureInitialized()) {
return null;
}
return properties.get(key);
}
public Map<String, String> getProperties() {
if (! ensureInitialized()) {
return Collections.emptyMap();
}
return properties;
}
public String getName() {
return getClass().getName();
}
public static void main(String[] args) {
ConfigProvider.getConfig();
}
} |
Ah, well that part of the issue could be fixed if we (either in SmallRye or in the spec as well) require that |
Right, so that brings us full circle to the start of this issue: there's no specified way for a
How would you propose doing this? Seems like there's a lot of opportunity for ambiguity... |
I think it would be reasonable for the To support this in the specification would require an SPI to be added to public static void withConfig(Config config, Runnable action) { ... } To support this in an implementation rather than in the specification, it can all be internal (but it would only work correctly if implementations are not mixed - which is normally not a problem because it is only allowed to have one Either way, if no "current" config is set, then the configuration would be selected based on application as it is done today. |
@dmlloyd digging a little deeper, the reason for the stack overflow in the example above is because the default for Anyway, I still feel like this is better solved by NOT enabling the use of this pattern. I think it would cleaner to insist that sources be ready to provide actual properties before they are enlisted into a config and provide a way for source providers to have access to already enlisted sources when they are invoked. I think there are basically two categories of public interface Configurable {
void configure(Config config);
} Any discovered source providers that also implement Then we could simply specify that configurable source providers can ONLY count on directly instantiated sources or those provided by non-configurable providers. |
I agree that it's best if sources are ready to provide properties before being enlisted into a
So this would appear to imply that the |
No, I don't want that either. I think there should be a single immutable snapshot of the config created just prior to invoking any configurable source providers. This snapshot gets passed to the source providers via the configure method and is only ever used there. |
OK, so this begs the question of whether we actually need any spec-level support for this to begin with? After all, one can always create a configuration, and then use that one to make another one, and register only the final configuration with the application. |
As a developer of a portable microprofile application, I want to |
No, there is no specified way to do this that I am aware of. |
So, I would argue that we DO need spec level support for this concept. Quarkus is a good example of where the lack of configuration "tiers" is problematic. If I deploy a custom config source in Quarkus, it gets instantiated 3 times per deployment and the lifecycle is not clearly specified or enforced (AFAIK). As more frameworks unify around the microprofile config model, I think it's important to try and fold some of these concepts into the core spec so portability can be preserved. |
As I'm back working on my application code, it occurs to me that my particular friction with the Config API could be solved in the CDI space. MicroProfile embraces CDI so it's natural for application developers who want to augment the Config to do so using CDI. #125 started some thinking along those lines, but doesn't seem to have gone anywhere... |
Not sure if CDI would be the proper way to go either. While it would definitely solve the issue, remember that the Config API must also work without a CDI container, so I believe a solution for this should cover both cases (with and without CDI). |
I agree. I think the idea of a tiered system should be addressed in both places. However, in my particular use case (and the bulk of application use cases, I believe) could be more easily accommodated in CDI. We can reasonably assume that there is a |
Hum, maybe I didn't understand correctly. I would you handle the tiered system when you don't have CDI? |
I'm just suggesting that the need for a tiered system exists in both places - the core API with the service loader mechanism, and CDI with scoped/typed beans. I've already presented some ideas in this thread for a non-CDI approach, but I've shifted my personal focus to a CDI solution since that's where my particular interest lies. |
I understand. So, you think that both should different solutions? Ideally if we could find something that works for both it would be great, but yes, in CDI it might be tricky. |
Yes. I think whatever we come up with in the base API is naturally available at the CDI level, but there's an additional opportunity in CDI to embrace the use of CDI beans to contribute sources/converters. I'm working on POC that I'm pretty happy with and hope to share it soon. |
Implemented a feature in SmallRye Config to support this: |
I'm keen to see this issue resolved as I also heard other users wanting to have this kind of support. Feel free to do a PR for further discussion! |
Not exactly this use case, but maybe helpful for people landing here: If you just need a value from an other property as a part for any property you wish to define in a |
to be replaced, once there is an official way to access higher-order config (see eclipse/microprofile-config#470)
to be replaced, once there is an official way to access higher-order config (see eclipse/microprofile-config#470)
It would be nice if it was possible for
ConfigSourceProvider
instances to read the currentConfig
of higher ordinal sources for use in defining it.For instance, a config property in system or
microprofile-config.properties
that specifies a list of directories that contain config elements, which aConfigSourceProvider
can reference to retrieve. At present there's no way to do this, meaning values need to be hard coded into the instance directly as to where something might be.Might require a "piecemeal" construction of
Config
so that each lower ordinal can take theConfig
up to that point and use itThe text was updated successfully, but these errors were encountered: