#  Kungfuig [](https://kantox.com/)  
## Intro
Live config supporting many different backends.
**Kungfuig** (_pronounced:_ [ˌkʌŋˈfig]) provides an easy way to plug
live configuration into everything.
It provides backends for `env` and `system` and supports custom backends. Configurations are periodically updated, and changes can trigger callback notifications to relevant application components.
## Installation
```elixir
def deps do
[
{:kungfuig, "~> 1.0"}
]
end
```
## Architecture
Kungfuig is designed with a robust architecture that provides flexibility and reliability:

The architecture consists of several key components:
1. **Supervisor** (`Kungfuig.Supervisor`): Manages the configuration process tree
2. **Manager** (`Kungfuig.Manager`): Supervises the backend workers
3. **Backend Workers** (`Kungfuig.Backend` implementations): Poll configuration sources
4. **Blender** (`Kungfuig.Blender`): Combines configurations from all backends
5. **Callbacks**: Notify application components when configurations change
6. **Validators**: Ensure configuration values match expected schemas
This architecture provides several benefits:
- Fault tolerance through supervisor patterns
- Separation of concerns between configuration sources
- Unified configuration access for your application
- Pluggable backends for custom configuration sources
## Using
### Basic Usage
**Kungfuig** is the easy way to read the external configuration from sources that are not controlled by the application using it, such as _Redis_, or _Database_.
```elixir
# Start Kungfuig with default options
Kungfuig.start_link()
# Get all configuration
Kungfuig.config()
#⇒ %{env: %{kungfuig: []}, system: %{}}
# Get configuration from a specific backend
Kungfuig.config(:env)
#⇒ %{kungfuig: []}
# Update application environment and see the change
Application.put_env(:kungfuig, :foo, 42)
Kungfuig.config(:env)
#⇒ %{kungfuig: [foo: 42]}
```
### Custom Backends
Here is an example of a backend implementation for reading configuration from an external MySQL database:
```elixir
defmodule MyApp.Kungfuig.MySQL do
@moduledoc false
use Kungfuig.Backend, interval: 300_000 # 5 minutes
@impl Kungfuig.Backend
def get(_meta) do
with {:ok, host} <- System.fetch_env("MYSQL_HOST"),
{:ok, db} <- System.fetch_env("MYSQL_DB"),
{:ok, user} <- System.fetch_env("MYSQL_USER"),
{:ok, pass} <- System.fetch_env("MYSQL_PASS"),
{:ok, pid} when is_pid(pid) <-
MyXQL.start_link(hostname: host, database: db, username: user, password: pass),
result <- MyXQL.query!(pid, "SELECT * FROM some_table") do
GenServer.stop(pid)
result =
result.rows
|> Flow.from_enumerable()
|> Flow.map(fn [_, field1, field2, _, _] -> {field1, field2} end)
|> Flow.partition(key: &elem(&1, 0))
|> Flow.reduce(fn -> %{} end, fn {field1, field2}, acc ->
Map.update(
acc,
String.to_existing_atom(field1),
[field2],
&[field2 | &1]
)
end)
Logger.info("Loaded #{Enum.count(result)} values from " <> host)
{:ok, result}
else
:error ->
Logger.warn("Skipped reconfig, one of MYSQL_{HOST,DB,USER,PASS} is missing")
:ok
error ->
Logger.error("Reconfiguring failed. Error: " <> inspect(error))
{:error, error}
end
end
end
```
### Callback Patterns
Kungfuig supports multiple callback mechanisms to notify your application when configuration changes:
#### Process Message Callback
```elixir
# Using a PID - the process will receive a {:kungfuig_update, config} message
defmodule MyApp.ConfigConsumer do
use GenServer
def start_link(_), do: GenServer.start_link(__MODULE__, nil)
def init(_), do: {:ok, %{}}
# Handle configuration updates
def handle_info({:kungfuig_update, config}, state) do
IO.puts("Configuration updated: #{inspect(config)}")
{:noreply, state}
end
end
# In your application startup
{:ok, pid} = MyApp.ConfigConsumer.start_link([])
Kungfuig.start_link(workers: [{Kungfuig.Backends.Env, callback: pid}])
```
#### Callback Behaviour Implementation
```elixir
defmodule MyApp.ConfigHandler do
@behaviour Kungfuig.Callback
@impl true
def handle_config_update(config) do
IO.puts("Configuration updated: #{inspect(config)}")
:ok
end
end
# In your application startup
Kungfuig.start_link(workers: [{Kungfuig.Backends.Env, callback: MyApp.ConfigHandler}])
```
#### GenServer Interaction Callbacks
```elixir
# Using a GenServer cast
defmodule MyApp.ConfigManager do
use GenServer
def start_link(_), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)
def init(_), do: {:ok, %{}}
def handle_cast({:config_updated, config}, state) do
# Process configuration update
{:noreply, Map.put(state, :config, config)}
end
end
# In your application startup
MyApp.ConfigManager.start_link([])
Kungfuig.start_link(
workers: [
{Kungfuig.Backends.Env, callback: {MyApp.ConfigManager, {:cast, :config_updated}}}
]
)
```
#### Function Callback
```elixir
# Using an anonymous function
callback_fn = fn config ->
IO.puts("Config updated: #{inspect(config)}")
:ok
end
Kungfuig.start_link(workers: [{Kungfuig.Backends.Env, callback: callback_fn}])
```
### Validation System
Since v0.3.0, Kungfuig supports configuration validation through the `Kungfuig.Validator` behavior, which by default uses `NimbleOptions` for schema validation.
#### Creating a Custom Validator
```elixir
defmodule MyApp.ConfigValidator do
use Kungfuig.Validator, schema: [
database: [
type: :string,
required: true,
doc: "The database name"
],
hostname: [
type: :string,
required: true,
doc: "The database hostname"
],
port: [
type: :integer,
default: 5432,
doc: "The database port"
],
ssl: [
type: :boolean,
default: false,
doc: "Whether to use SSL"
]
]
end
```
#### Using the Validator
You can apply validators at two levels:
1. **Per backend** - to validate only this backend's configuration:
```elixir
Kungfuig.start_link(
workers: [
{MyApp.Kungfuig.DatabaseBackend, validator: MyApp.ConfigValidator}
]
)
```
2. **Global validator** - to validate the entire configuration:
```elixir
Kungfuig.start_link(
validator: MyApp.GlobalConfigValidator,
workers: [
MyApp.Kungfuig.DatabaseBackend,
MyApp.Kungfuig.RedisBackend
]
)
```
### Named Instances
Since v0.4.0, Kungfuig supports multiple named instances, allowing different parts of your application to use separate configuration managers.
```elixir
# Start a Kungfuig instance for database configuration
Kungfuig.start_link(
name: MyApp.DBConfig,
workers: [
{MyApp.Kungfuig.DatabaseBackend, interval: 60_000}
]
)
# Start another Kungfuig instance for API configuration
Kungfuig.start_link(
name: MyApp.APIConfig,
workers: [
{MyApp.Kungfuig.APIBackend, interval: 30_000}
]
)
# Query the configurations separately
db_config = Kungfuig.config(:database, MyApp.DBConfig)
api_config = Kungfuig.config(:api, MyApp.APIConfig)
```
### Immediate Configuration Processing
Since v0.4.2, Kungfuig allows immediate configuration validation using the `imminent: true` option:
```elixir
Kungfuig.start_link(
workers: [
{Kungfuig.Backends.Env, imminent: true}
]
)
```
This option ensures that the configuration is validated and processed during the init phase, rather than as a continuation after init returns. This is useful when your application requires the configuration to be available immediately upon startup.
## Testing
Simply implement a stub returning an expected config and you are all set.
```elixir
defmodule MyApp.Kungfuig.Stub do
@moduledoc false
use Kungfuig.Backend
@impl Kungfuig.Backend
def get(_meta), do: %{foo: :bar, baz: [42]}
end
```
## Comparison with Alternatives
Kungfuig offers several advantages compared to other configuration management solutions:
### vs. Application Environment
The standard Elixir Application environment is static after startup unless manually changed:
- **Kungfuig**: Automatically polls for changes at configurable intervals
- **App env**: Requires manual calls to `Application.put_env/3` to update
### vs. Config Providers
Elixir's built-in config providers run only once at application start:
- **Kungfuig**: Continually monitors for changes while the application is running
- **Config providers**: Only run during application startup
### vs. Distillery Config Providers
While Distillery's Config Providers can load configuration on application start:
- **Kungfuig**: Lets you define custom backends for any data source
- **Distillery**: Focused on files and environment variables
- **Kungfuig**: Supports multiple callback types for change notifications
- **Distillery**: Doesn't have built-in change notification
### vs. etcd/Consul/ZooKeeper clients
While these external services provide great configuration management:
- **Kungfuig**: Works with many different backends, not tied to a specific service
- **External services**: Require running and maintaining additional infrastructure
- **Kungfuig**: Simple supervision tree model familiar to Elixir developers
- **External services**: More complex client libraries and connection management
## Changelog
- **`1.0.0`** — modern Elixir v1.16
- **`0.4.4`** — fix a bug with hardcoded names (`Supervisor` and `Blender`)
- **`0.4.2`** — allow `imminent: true` option to `Kungfuig.Backend`
- **`0.4.0`** — allow named `Kungfuig` instances (thanks @vbroskas)
- **`0.3.0`** — allow validation through `NimbleOptions` (per backend and global)
- **`0.2.0`** — scaffold for backends + several callbacks (and the automatic one for `Blender`)
## [Documentation](https://hexdocs.pm/kungfuig)