Ptolemy v0.2.0 Ptolemy.Loader View Source

Ptolemy.Loader implements a highly opinionated Application Configuration solution.

Instead of having compile-time configuration and secrets, or simple system environment variables on application startup, this module provides infrastructure on loading configuration from anywhere, with the bonus support of dynamic configurations.

Basics

Tell Loader what and where your configuration values should go. This is done in config.exs:

alias Ptolemy.Providers.SystemEnv
config :ptolemy, loader: [
  env: [
    {{:app_name, :secret_key}, {SystemEnv, "PATH"}},
    # ...
  ]
]

The above configuration will result in the system environment variable PATH being set to your application's :secret_key value. It can be retrieved at any time afterwards with Application.get_env(:app_name, :secret_key)

To start your application with the loader, simply add it as the first process under your application supervision tree.

  # add to your child process list in application.ex or other top-level supervising process
  children = [
    Ptolemy.Loader,
    # ...
  ]

This will populate your application's key/value store for all following processes. It is important to note that the one caveat to loading configuration this way is that the Loader will block the startup of the remainder of the supervision tree until initial values have been loaded into the application. This will most likely lead to slightly longer startup times, depending on the providers used. All updates the providers notify the loader of will be handled concurrently.

Nested Configurations

Nested configurations are also supported by Ptolemy.Loader. To achieve the equivalent configuration as:

config :app_name, top_key: [
  first_nest: %{
    target_key: "hello!"
  }
]

The loader configuration would be similar to:

config :app_name, top_key: [
  first_nest: %{
    target_key: "dummy_value"
  }
]

alias Ptolemy.Providers.SystemEnv
config :ptolemy, loader: [
  env: [
    {{:app_name, [:top_key, :first_nest, :target_key]}, {SystemEnv, "TARGET_VAR"}}
  ]
]

The loader can only populate configuration values with no stub if the value is stored as the top level value. Once the first value stored in a configuration is a structure, loader will not be able to imply what structure the value is expected to be stored in. The dummy value is included in the stub to be explicit; but only the surrounding structure is required. For example, the configuration below will also work:

config :app_name, top_key: [
  first_nest: %{}
]

The loader will make no assumptions on the structure of configurations. It will raise an error on initialization if the structure can not be updated to ensure configuration is always as intended after loader was initialized.

Built-In Providers

Providers that ship with Ptolemy include:

Performance Considerations

The best practices implied by the purpose of Ptolemy.Loader is that Application.get_env/2 should be called repeatedly at runtime whenever configuration dependent code is executed. This raises the question of performance impacts on that dependent code from constantly calling a lookup function. As explored in this article, you may incur small costs on massively frequent invocations and/or large return values, however at the time of writing these docs, it is felt that this is an acceptable price to pay. If ever the case does arise where there is a performance bottleneck, support for application environment will not be replaced to preserve integration with third party libraries.

Link to this section Summary

Types

The target configuration to be updated by a provider

The specification to query a provider

Functions

Returns a specification to start this module under a supervisor

Retrieves the configuration of the loader

Initializes the process's state

Invokes a provider with a query and sets the result to the mapped application environment target

Link to this section Types

Link to this type

config_target() View Source
config_target() :: {atom(), atom() | [atom()]}

The target configuration to be updated by a provider.

Targets are mapped to be later retrieved from Application.get_env/2.

Link to this type

provider_spec() View Source
provider_spec() :: {module(), any()}

The specification to query a provider.

Link to this section Functions

Returns a specification to start this module under a supervisor.

See Supervisor.

Retrieves the configuration of the loader.

Initializes the process's state.

This process is a special case where the state will already be built in the same process as the supervisor to intentionally delay other processes from starting when loading configuration.

Link to this function

load(config_target, provider_spec) View Source
load(config_target(), provider_spec()) :: :ok

Invokes a provider with a query and sets the result to the mapped application environment target.

Link to this function

start_link(config \\ Application.get_env(:ptolemy, :loader)) View Source

Starts the Loader process.

While still functioning as a typical start_link/1 helper, this implementation also contains blocking business logic to ensure subsequent processes can retrieve populated application state values.