Choice of Configuration API

Hello all,

This proposal concerns the choice of a Config API.

The design page: https://design.xwiki.org/xwiki/bin/view/Proposal/ConfigAPI

We have three options:

  1. Directly use one of the available configuration API.
  2. Define our own configuration API:
    1. And delegate the implementation to an available configuration API
    2. And define our own implementation.

My research of configuration libraries shows a variety of options (e.g., @configu/browser, dotenv, or config).
But, they are all designed in a way that make them usable only usable in a node environment (i.e., outside a browser).
Therefore, I don’t think this option can be considered. Consequently, option 2.1 is also not possible for the same reasons.
Knowing that, I suggest going with our own configuration API.

You can find below some more design requirements and constraints for the design of this API, as well as a snippet of the API interface.

Requirements:

  • Able to run in the browser
  • Compatible with typescript
  • Being able to load a value from a hierarchical set of sources (e.g., a properties file, an XObject…)
  • Being able to re-load values at runtime (e.g., an XObject property is updated)
  • Preferable: efficient caching mechanism

Use-cases:

A lot of those use-cases does not surface at the API level, but I’m mentioning to give a better view of what I’m aiming for.

  • values can change at runtime
  • ability to define hierarchies of configurations
  • each config component is in charge of defining if it must be loaded first (i.e., at startup or lazily), if it can change over-time, and a when the value can be invalidated
  • no need for a notion of right, if a value is returned client-side and shouldn’t be visible to the current user, it’s a security issue on the server
  • the API is read-only, the API user has now way to change the configuration value through this API, and must instead use APIs to edit the underlying source to change a config value
  • as values can be fetched on-demand from the server (e.g., from a rest endpoint), any configuration result must be wrapped in a Promise, and should be expected to fail for external reasons (e.g., punctual networks issue)

API Example

/**
 * Defines a common set of methods to access for configuration values.
 *
 * @since 0.6
 */
interface ConfigurationSource {

    /**
     * Get a configuration value for the given key. Fallbacks to the default value if no value is found.
     *
     * @param key the configuration key to access
     * @param defaultValue a default value if no value is found for the request key
     * @return a promise with the configuration value
     * @throws ConfigurationException in case of issue when accessing the configuration
     */
    getProperty<T>(key: string, defaultValue?: T): Promise<T | undefined>;

    /**
     * Returns `true` if the configuration source has the given key, `false` otherwise.
     *
     * @param key the configuration key to check the presence of
     * @return a promise with `true` if the configuration source has the given key, `false` otherwise`
     */
    hasProperty(key: string): Promise<boolean>
}

/**
 * Exception thrown in case of configuration issue.
 *
 * @since 0.6
 */
class ConfigurationException extends Error {

}

Note: the design page has a more extensive snippet including implementation and tests.

WDYT?

Hi,

what exactly does this requirement means? Isn’t it enough if you have a REST API endpoint dedicated to request a configuration value and then it’s resolved in backend?

ability to define hierarchies of configurations

Just a small note because I haven’t seen it mentioned, but remember that in XS we’re starting to see the need of limiting ability to override some configuration (e.g. the mail configuration of a subwiki), so might be interesting to keep that in mind when defining a new design.

Let me expand. Broadly, a library can be expected to be executed in two contexts:

  1. in a node.js interpreter
  2. in the interpreter of the browser (what I call “in the browser”)

The available APIs are not the same, and libraries expecting to run in node often fail at runtime when executed in the browser because they are expecting APIs that only available in node.

And, this is the case of all the logging libraries I could find. For instance, they require APIs with direct access to the file system, to persist logs. And, because JavaScript running in the browser is sandboxed, this not the case.

Regarding the REST endpoint, this is an implementation detail. Some configurations source will directly access json available in the dom. Others will call REST endpoints. But that’s the choice of the configuration source.
What’s important is that the configuration source goes thought generic API to access the configuration values, as this might depend on the capabilities of the backend (e.g., XWiki can easily serve rest APIs, while Cristal using a filesystem backend will not be able to access rest APIs).

I don’t think this impact the Configuration API, but this might have impact on the way we use it. I’ll keep that in mind. Thanks.

IMO we need the ability to store the cristal configs somewhere (might diff if using the electron app - file system somewhere, vs the browser - local storage or cookies).

Some back end may support storing configs while others may not. Another idea would to have a server for storing configs for back ends that don’t support that. Use case: be able to get back your configs if you switch from electron app to web browser or if you switch from one computer to another.

Definitely.
We could have Config implementations for:

  • cookies
  • local storage
  • local file (e.g., for electron)
  • remote rest endpoint
  • third-party configuration storage

Each configuration has its own pros and cons:

  • cookies and local storage and only available for the current browser, so good for configuration that can be lost when switching to another browser
  • local files are great but only available for electron
  • remote rest endpoint are good to persist configurations that should not be lost when switching to another browser
  • third-party configuration storage is a good alternative to remove rest endpoint when the back-end does not provide the relevant rest endpoints
  • remote rest endpoint