我需要通过我的应用程序分发某种静态配置。这样做的最佳做法是什么?
我看到三个选择:
application:get_env
。
另外:比其他选项更简单。
减:如何在不引入整个应用程序的情况下测试这些模块?
减:如何启动具有不同配置的某些模块(如果需要)?application:get_env
检索)传递给应用程序模块。
另外:模块更容易测试,您可以使用不同的配置启动它们。
减:很多样板代码。更改配置格式需要修复多个位置。另一种方法是将配置数据转换为Erlang源模块,通过导出使配置数据可用。然后,您只需加载新版本的配置模块,即可在运行的系统中随时更改配置。
对于我自己项目中的静态配置,我喜欢选项(1)。我将向您展示我在名为max_widgets
的应用程序中访问名为factory
的配置参数所采取的步骤。
首先,我们将创建一个名为factory_env
的模块,其中包含以下内容:
-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).
接下来,在需要读取max_widgets
的模块中,我们将定义如下的宏:
-define(MAX_WIDGETS, factory_env:get_env(max_widgets, 1000)).
这种方法有一些好处:
application:set_env/3
和application:get_env/2
,所以我们实际上并不需要启动factory
应用程序才能通过测试。max_widgets
获取默认值,因此即使未定义参数,我们的代码仍然可以工作。max_widgets
使用不同的默认值。最后,当我们准备部署时,我们将在sys.config目录中放置一个priv
文件,并在启动时使用-config priv/sys.config
加载它。这允许我们根据需要在每个节点上更改配置参数。这样可以将配置与代码完全分开 - 例如我们不需要再进行一次提交就可以将max_widgets
更改为500。
您可以使用进程(gen_server可能?)将配置参数存储在其状态中。它应该公开一个get / set接口。如果尚未显式设置值,则应检索默认值。
-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.
...
然后,您可以在测试中轻松模拟此模块。在运行时使用一些新配置更新过程也很容易。
您可以使用模式匹配和元组将不同的配置参数关联到不同的模块:
set({ModuleName, ParamName}, Value) ->
...
get({ModuleName, ParamName}) ->
...
将该过程置于监督树下,因此它将在所有其他需要配置的过程之前启动。
哦,我很高兴到目前为止没有人建议parametrized modules :)
我会为静态配置做选项1。您可以随时通过application:set_env/3,4
设置选项进行测试。您希望这样做的原因是您的应用程序测试需要在某个时间运行整个应用程序。此时设置特定于测试的配置的能力非常好。
应用程序控制器默认运行,因此您不需要按应用程序方式运行(无论如何也需要这样做!)
最后,如果进程需要特定配置,请在配置数据中这样说!您可以存储任何Erlang项,特别是您可以存储一个术语,使您能够覆盖特定节点的配置参数。
对于动态配置,最好使用gen_server
或使用最新的gproc
功能来存储这样的动态配置。
我还看到人们使用.hrl(erlang头文件),其中定义了所有配置并将其包含在需要配置的任何文件的开头。
它使得配置查找非常简洁,并且您可以获得任意复杂性的配置。
我相信你也可以通过执行模块的热代码重载来在运行时重新加载配置。缺点是如果您在多个模块中使用配置并仅重新加载其中一个模块,则只有一个模块将更新其配置。
但是,我实际上没有检查它是否像那样工作,我找不到关于.hrl和热代码重新加载如何交互的确切文档,所以一定要在实际使用它之前仔细检查一下。