19

I need to distribute some sort of static configuration through my application. What is the best practice to do that?

I see three options:

  1. Call application:get_env directly whenever a module requires to get configuration value.
    • Plus: simpler than other options.
    • Minus: how to test such modules without bringing the whole application thing up?
    • Minus: how to start certain module with different configuration (if required)?
  2. Pass the configuration (retrieved from application:get_env), to application modules during start-up.
    • Plus: modules are easier to test, you can start them with different configuration.
    • Minus: lot of boilerplate code. Changing the configuration format requires fixing several places.
  3. Hold the configuration inside separate configuration process.
    • Plus: more-or-less type-safe approch. Easier to track where certain parameter is used and change those places.
    • Minus: need to bring up configuration process before running the modules.
    • Minus: how to start certain module with different configuration (if required)?
Ivan Dubrov
  • 4,778
  • 2
  • 29
  • 41
  • Have you thought about the implications of starting a node programmatically (using ct_slave, for instance)? Changing the .app or .config files is messy, and utilizing the "static" module approach is impractical... I'm still trying to figure out the best way to do this... – pedromanoel Jan 26 '12 at 20:13
  • I must of (or my cat) managed to vote this down by mistake, and now apparently my decision is locked... Sorry. – expelledboy Nov 21 '13 at 08:25

5 Answers5

13

Another approach is to transform your configuration data into an Erlang source module that makes the configuration data available through exports. Then you can change the configuration at any time in a running system by simply loading a new version of the configuration module.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 1
    Neat idea, thanks! The only concern is that for the testing purposes I will need several modules with same name, which could be confusing. – Ivan Dubrov Jun 05 '11 at 10:50
  • 3
    You could either use [meck](https://github.com/eproxus/meck) to create mock modules during testing (shameless self plug, I'm the author), or parameterize the module names in your code (which is really neat for testing). – Adam Lindberg Jun 06 '11 at 10:02
  • What exist libraries for transform your configuration data into an Erlang source? – Habibutsu May 12 '14 at 13:09
  • @Habibutsu: I don't know of any libraries or tools to do that. I meant manually transform your configuration into something that is suitable for your Erlang app. – Greg Hewgill May 12 '14 at 17:38
10

For static configuration in my own projects, I like option (1). I'll show you the steps I take to access a configuration parameter called max_widgets in an application called factory.

First, we'll create a module called factory_env which contains the following:

-define(APPLICATION, factory).

get_env(Key, Default) ->
    case application:get_env(?APPLICATION, Key) of
        {ok, Value} -> Value;
        undefined -> Default
    end.

set_env(Key, Value) ->
    application:set_env(?APPLICATION, Key, Value).

Next, in a module that needs to read max_widgets we'll define a macro like the following:

-define(MAX_WIDGETS, factory_env:get_env(max_widgets, 1000)).

There are a few nice things about this approach:

  • Because we used application:set_env/3 and application:get_env/2, we don't actually need to start the factory application in order to have our tests pass.
  • max_widgets gets a default value, so our code will still work even if the parameter isn't defined.
  • A second module could use a different default value for max_widgets.

Finally, when we are ready to deploy, we'll put a sys.config file in our priv directory and load it with -config priv/sys.config during startup. This allows us to change configuration parameters on a per-node basis if desired. This cleanly separates configuration from code - e.g. we don't need to make another commit in order to change max_widgets to 500.

David Weldon
  • 63,632
  • 11
  • 148
  • 146
7

You could use a process (a gen_server maybe?) to store your configuration parameters in its state. It should expose a get/set interface. If a value hasn't been explicitly set, it should retrieve a default value.

-export([get/1, set/2]).

...

get(Param) ->
  gen_server:call(?MODULE, {get, Param}).

...

handle_call({get, Param}, _From, State) ->
  case lookup(Param, State#state.params) of
    undefined ->
      application:get_env(...);
    Value ->
      {ok, Value}
  end.

...

You could then easily mockup this module in your tests. It will also be easy to update the process with some new configuration at run-time.

You could use pattern matching and tuples to associate different configuration parameters to different modules:

set({ModuleName, ParamName}, Value) ->
  ...

get({ModuleName, ParamName}) ->
  ...

Put the process under a supervision tree, so it's started before all the other processes which are going to need the configuration.

Oh, I'm glad nobody suggested parametrized modules so far :)

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Roberto Aloi
  • 30,570
  • 21
  • 75
  • 112
3

I'd do option 1 for static configuration. You can always test by setting options via application:set_env/3,4. The reason you want to do this is that your tests of the application will need to run the whole application anyway at some time. And the ability to set test-specific configuration at that point is really neat.

The application controller runs by default, so it is not a problem that you need to go the application-way (you need to do that anyway too!)

Finally, if a process needs specific configuration, say so in the configuration data! You can store any Erlang-term, in particular, you can store a term which makes you able to override configuration parameters for a specific node.

For dynamic configuration, you are probably better off by using a gen_server or using the newest gproc features that lets you store such dynamic configuration.

I GIVE CRAP ANSWERS
  • 18,739
  • 3
  • 42
  • 47
0

I've also seen people use a .hrl (erlang header file) where all the configuration is defined and include it at the start of any file that needs configuration.

It makes for very concise configuration lookups, and you get configuration of arbitrary complexity.

I believe you can also reload configuration at runtime by performing hot code reloading of the module. The disadvantage is that if you use configuration in several modules and reload only one of them, only that one module will get its configuration updated.

However, I haven't actually checked if it works like that, and I couldn't find definitive documentation on how .hrl and hot code reloading interact, so make sure to double-check this before you actually use it.

Shnatsel
  • 4,008
  • 1
  • 24
  • 25