Settings
is a generic settings API for top-level applications, intended to
solve a set of real-world issues with other approaches to run-time application
configuration.
Settings
provides an abstraction for the intricacies of settings, while
deferring the practicalities of persistence to the client via the
Settings.Backend
behaviour.
{:settings, "~> 0.1.0"},
Settings
currently ships with just an in-memory backend, which was required in
order to test Settings. You will need to write a module that implements the
Settings.Backend
behavior.
See the SimpleApp
example for very basic use.
A larger example of an umbrella project is in the works.
Apps are a grouping/namespacing construct for settings, most useful in the umbrella
app domain. When querying settings, queries may be restricted by app, e.g.
Settings.all(:myapp)
, which returns all settings for the app :myapp
The return value from get/1,2
is constrained by 'scope'. First, a simple
example:
iex> Settings.create(:http_timeout_ms, 15_000)
...> Settings.get(:http_timeout_ms) # :scope omitted, defaults to :__global
15000 # this is the :__default scope value, because :__global has not been set
...> Settings.set(:http_timeout_ms, 10_000) # :scope omitted, defaults to :__global
...> Settings.get(:http_timeout_ms)
10000 # this is the global scope value
...> Settings.set(:http_timeout_ms, 5_000, scope: :lan_requests)
...> Settings.get(:http_timeout_ms, scope: :lan_requests)
5000
...> Settings.get(:http_timeout_ms, scope: :wifi_requests)
10000 # falls back to :__global, as the :wifi_requests scope has not been set
As the example shows, a Setting may have multiple values, each known by their
:scope
. :__default
is the scope set during Settings.create
, and can only
be changed by calling Settings.create
again (usually when the app is
restarted). :__global
is the scope used whenever the :scope
opt is omitted,
and takes precedence over :__default
. Finally, specific scopes may be set by
using the :scope
opt in get/2
and set/3
, and accept any Elixir term.
During retrieval, the value for the requested scope will be returned if it
exists, followed by the :__global
scope if it exists, with :__default
as the
ultimate fallback.
The create
method must be called each time the app is started, prior to
any other calls which access the setting. While this may seem like a stifling
requirement, it is a byproduct of a common pattern in the development process:
- identify that a hardcoded value should be a run-time setting
- create the setting, and assign it the value of, e.g. 5
- test and troubleshoot, then realize that the value should have been 7
We cannot use set
at this moment, because the value needs to be changed for
the production system, not just in the persistence layer on this developer's
machine. If the developer now changes the value in the create method to 7, the
source code will reflect that change and can be propagated via source control.
A useful pattern is to create a module/method to be called during app startup
that contains all of the calls to Settings.create
, which forms a convenient
place to go to see the available configuration for an app.
# This allows us to omit these opts from the remaining calls, and affects the
# entire node.
Settings.set_defaults(app: :my_webapp, backend: MyEctoBackend)
# gets app and backend from `set_defaults`
Settings.create(:site_url, "www.foo.com")
# still uses backend from `set_defaults`, but overrides the app
Settings.create(:gps_device, "ttyUSB0", app: :my_serial_app)
This library was designed using Artifact. The raw design files are located
in ./design
, and they have been rendered here.