ConfigJSR icon indicating copy to clipboard operation
ConfigJSR copied to clipboard

Provide a mechanism to detect ConfigValue changes

Open struberg opened this issue 7 years ago • 8 comments

Splitting this out from #9 into a separate ticket as discussed in our last EG meeting.

When people make use of ConfigValue they often want to trigger some action when a new value get's picked up. Remember that ConfigValue is only needed for configured values which might change during runtime.

Often a user wants to be aware when the underlying value got changed. Either to log it out, or to perform some other code.

This is essentially done by registering a lambda as callback which gets called whenver the underlying config gets accessed an a change gets detected. https://github.com/struberg/ConfigJSR-1/blob/i9_configValue/api/src/main/java/javax/config/ConfigValue.java#L162

Here is a code examples to highlight why this is an important use case:

@ApplicationScoped
public class FtpService {
  private ConfigValue<String> urlCfg;

  @Inject 
  void setConfig(Config cfg) {
    cfg.access("myapp.ftp.url")
      .cacheFor(1, TimeUnits.DAY)
      .evaluateVariables(true)
      .withLookupChain("${myapp.tenant}")
      .onChange((propertyName, oldV, newV) 
          -> log.info("Switchint to a new FTP URL from {} to {}", oldV, newV));
  }

  public void store(String name, byte[] content) {
    FtpConnection = access(urlCfg.getValue());
  }
}

This is really important for our Ops team. Otherwise they don't really have a clue when the system eventually picked up new values!

The onChange callback might also be used to e.g. clean cached values, etc. It's really very versatile and an important use case!

struberg avatar May 08 '18 08:05 struberg

This is a useful addition in general. Concise API, easy to implement.

However, the downside as I understand it is that it will only catch changes made from the Config API, but not external changes to ConfigSources, say etcd or a database. So it must be clearly to users that external changes might not be catched.

While not 100% removing the problem, a companying addition might be a mean for a ConfigSource to emit change notifications when the underlying store supports such detection/notification. The problem there is how to propagate such notification, since it might not be part of the effective configuration if a source with higher precedence masks the property - in this case the it would be counter intuitive for the application to receive a change notification, since the effective application hasn't changed.

rgielen avatar May 09 '18 07:05 rgielen

If this is a throwaway example don't worry, but otherwise "Switchint..." should be "Switching...".

keilw avatar May 10 '18 00:05 keilw

Hi @rgielen! Thanks for your feedback.

Yes, it's not intended to get actively notified asynchronously if anything deep inside the Config changes. It's really just a callback for the case when you call getValue and some change got detected. So now you can actively do something, like verifying the scenario, logging out the changes, etc

struberg avatar May 23 '18 05:05 struberg

So do I understand correctly that to trigger an event when a value is changed (or soon after), it's necessary to poll config for that value? Something like this:

@Schedule(hour="*", minute="*", second="*") // every second
public void pollMyConfig() {
    ConfigProvider.getConfig().getOptionalValue("my.config", String.class);
}

If yes, then it's cool because it's clear that the listeners are executed in the current thread and no other thread needs to be created.

However, I would consider adding special methods for this to make it more obvious. For example: config.propertyExists(String) (and Javadoc explaining that this also detects changes and triggers onChange event) and also config.propertyExistsAsync(String, Executor) to allow triggering onChange events in a separate executor.

OndroMih avatar May 24 '18 21:05 OndroMih

Yes, it's fully synchronous on the same thread. That has benefits and of course the downside that you really only get the callback if the getValue() or getOptionalValue() get invoked.

It's basically internally the following code

T getValue() {
  if (onChange != null && !saveEquals(oldValue, newValue) {
    onChange(key, oldValue, newValue);
  }
  ..
  return newValue;
}

So it's not a full replacement for the Config#registerOnConfigChange we had in earlier versions. This would have been fully 'pro-active', but also would have added quite a few new problems, like getting notified asynchronously on a new thread without any EE set up, etc...

Oh and thanks for the feedback!

struberg avatar May 25 '18 08:05 struberg

Oh and I got asked for other use cases. The things I've seen is

  • logging a config value change
  • creating a new Client after the config changed
  • cleaning caches on a config change
  • cleaning statistics on a config change
  • perform an eager sanity check (e.g. if endpoint exists) + fallback logic on a config change.

struberg avatar May 25 '18 08:05 struberg

will work on it due to popular demand.

struberg avatar Jun 28 '18 13:06 struberg

Great, any news on the EDR?

keilw avatar Jun 28 '18 13:06 keilw