Kungfuig behaviour (Kungfuig v1.0.1)

View Source

Kungfuig provides a pluggable drop-in support for live configurations with change notification capabilities.

Overview

Kungfuig (pronounced: [ˌkʌŋˈfig]) is a comprehensive configuration management system for Elixir applications that provides:

  • Live configuration updates from multiple sources
  • Callback notifications when configurations change
  • Configuration validation
  • Extensible backend system for custom configuration sources
  • Supervisor-managed configuration processes

This behaviour defines the dynamic config provider. In most cases, you don't need to implement this behaviour directly. Instead, implement one or more backends (see Kungfuig.Backend) and pass them, optionally parametrized, as workers: to start_link/1 as shown below:

{:ok, pid} =
  Kungfuig.start_link(
    workers: [
      {Kungfuig.Backends.EnvTransform,
       interval: 100,
       validator: Kungfuig.Validators.EnvTransform,
       callback: {self(), {:info, :updated}},
       callback: Kungfuig.Test.CallbackAlert}
    ]
  )

By default, Kungfuig starts two backends:

Architecture

Kungfuig operates using a supervisor tree pattern:

  1. A supervisor (Kungfuig.Supervisor) manages the configuration process
  2. A manager process (Kungfuig.Manager) supervises the backend workers
  3. Backend workers (Kungfuig.Backend implementations) poll their respective configuration sources
  4. A blender process (Kungfuig.Blender) combines configurations from all backends
  5. Callbacks are triggered when configurations change

Custom Configuration Sources

If you need to get configuration updates from sources other than the default ones, such as JSON/YAML files, Redis, databases, or any external service, you can create your own backends by implementing the Kungfuig.Backend behaviour.

See Kungfuig.Backend for reference or check the Usage section in the README for an example using MySQL.

Validation

Since v0.3.0, Kungfuig supports validation through the Kungfuig.Validator behaviour, which by default uses NimbleOptions for schema validation. Validators can be specified:

  • Per backend (applying only to that backend's configuration)
  • Globally (applying to the entire configuration)

Callbacks

When a configuration changes, Kungfuig notifies interested parties through callbacks. Multiple callback mechanisms are supported, as detailed in the callback/0 type.

Named Instances

Since v0.4.0, Kungfuig supports multiple named instances, allowing different parts of your application to use separate configuration managers.

Examples

Basic usage:

# 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]}

Using callbacks:

# Using a process message as callback
Kungfuig.start_link(
  workers: [
    {Kungfuig.Backends.Env, callback: {self(), {:info, :config_updated}}}
  ]
)

# Using a module that implements Kungfuig.Callback
defmodule MyApp.ConfigHandler do
  @behaviour Kungfuig.Callback
  
  @impl true
  def handle_config_update(config) do
    IO.puts("Configuration updated: " <> inspect(config))
  end
end

Kungfuig.start_link(
  workers: [
    {Kungfuig.Backends.Env, callback: MyApp.ConfigHandler}
  ]
)

Using named instances (since v0.4.0):

# Start a named Kungfuig instance
Kungfuig.start_link(name: MyApp.Config)

# Query the named instance
Kungfuig.config(:env, MyApp.Config)

Using immediate validation (since v0.4.2):

# Start with imminent validation for immediate config processing
Kungfuig.start_link(
  workers: [
    {Kungfuig.Backends.Env, imminent: true, validator: MyApp.ConfigValidator}
  ]
)

Summary

Types

The callback to be used for subscribing to configuration updates.

The config map to be updated and exposed through callbacks.

The options that can be passed to start_link/1 function.

Functions

Retrieves the current config for the key(s) specified

The function to start the Kungfuig manager under a supervision tree with default options

The function to start the Kungfuig manager under a supervision tree with custom options

Types

callback()

@type callback() ::
  module()
  | pid()
  | {module(), atom()}
  | (config() -> :ok)
  | {GenServer.name() | pid(), {:call | :cast | :info, atom()}}

The callback to be used for subscribing to configuration updates.

Kungfuig supports several callback mechanisms:

  • Module implementing Kungfuig.Callback behaviour:

    defmodule MyApp.ConfigHandler do
      @behaviour Kungfuig.Callback
      
      @impl true
      def handle_config_update(config) do
        # Handle configuration update
        :ok
      end
    end
  • Process identifier (PID): The process will receive a {:kungfuig_update, config} message

    # In a GenServer
    def handle_info({:kungfuig_update, config}, state) do
      # Handle configuration update
      {:noreply, state}
    end
  • Module-function tuple: A tuple {module, function} where the function accepts a single argument (the configuration)

    defmodule MyApp.ConfigHandler do
      def on_config_update(config) do
        # Handle configuration update
      end
    end
    
    # In your Kungfuig setup
    callback: {MyApp.ConfigHandler, :on_config_update}
  • Anonymous function: A function that takes one argument (the configuration)

    callback: fn config -> 
      # Handle configuration update
      :ok 
    end
  • GenServer interaction tuple: A tuple specifying a GenServer and how to interact with it

    # Send a cast to a GenServer
    callback: {MyGenServer, {:cast, :config_updated}}
    
    # Inside the GenServer
    def handle_cast({:config_updated, config}, state) do
      # Handle configuration update
      {:noreply, state}
    end

config()

@type config() :: %{required(atom()) => term()}

The config map to be updated and exposed through callbacks.

This is a map where the keys are atoms (typically representing backend names) and the values are the configuration data from those backends.

option()

@type option() ::
  {:name, GenServer.name()}
  | {:callback, callback()}
  | {:interval, non_neg_integer()}
  | {:imminent, boolean()}
  | {:anonymous, boolean()}
  | {:start_options, GenServer.options()}
  | {:validator, module()}
  | {:workers, [module() | {module(), keyword()}]}

The options that can be passed to start_link/1 function.

Options include:

  • :name - The name to register the Kungfuig instance under. Default is Kungfuig. @since v0.4.0

  • :callback - One or more callbacks to notify when configuration changes. See callback/0.

  • :interval - How frequently to check for configuration updates, in milliseconds. Default is 1000 (1 second).

  • :imminent - When true, performs configuration validation immediately during initialization, rather than as a continuation after init returns. Default is false.

  • :anonymous - When true, the GenServer is not registered under a name. Default is false.

  • :start_options - Options passed to GenServer.start_link/3.

  • :validator - A module implementing the Kungfuig.Validator behaviour to validate the configuration. Default is Kungfuig.Validators.Void (no validation). @since v0.3.0

  • :workers - A list of backend specifications, each either a module name or a {module, opts} tuple. It might also be a function of arity

Functions

config(which \\ :!, supervisor \\ Kungfuig)

@spec config(which :: atom() | [atom()], supervisor :: Supervisor.supervisor()) ::
  config()

Retrieves the current config for the key(s) specified

start_link()

The function to start the Kungfuig manager under a supervision tree with default options

start_link(opts)

The function to start the Kungfuig manager under a supervision tree with custom options