使用环境变量构建配置 class 更简洁的方法?
More concise way to build a configuration class using environment variables?
我有一个 class Configuration
读取环境变量:
class Configuration {
has $.config_string_a;
has $.config_string_b;
has Bool $.config_flag_c;
method new() {
sub assertHasEnv(Str $envVar) {
die "environment variable $envVar must exist" unless %*ENV{$envVar}:exists;
}
assertHasEnv('CONFIG_STRING_A');
assertHasEnv('CONFIG_STRING_B');
assertHasEnv('CONFIG_FLAG_C');
return self.bless(
config_string_a => %*ENV{'CONFIG_STRING_A'},
config_string_b => %*ENV{'CONFIG_STRING_B'},
config_flag_c => Bool(%*ENV{'CONFIG_FLAG_C'}),
);
}
}
my $config = Configuration.new;
say $config.config_string_a;
say $config.config_string_b;
say $config.config_flag_c;
有没有更简洁的表达方式?例如,我在检查中重复环境变量名称和构造函数的 return 值
我可以很容易地看到编写另一个更通用的 class,它封装了配置参数的必要信息:
class ConfigurationParameter {
has $.name;
has $.envVarName;
has Bool $.required;
method new (:$name, :$envVarName, :$required = True) {
return self.bless(:$name, :$envVarName, :$required);
}
}
然后将这些滚动到 Configuration
class 中的列表中。但是,我不知道如何重构 Configuration
中的构造函数来适应这一点。
想到的最直接的变化是将 new
更改为:
method new() {
sub env(Str $envVar) {
%*ENV{$envVar} // die "environment variable $envVar must exist"
}
return self.bless(
config_string_a => env('CONFIG_STRING_A'),
config_string_b => env('CONFIG_STRING_B'),
config_flag_c => Bool(env('CONFIG_FLAG_C')),
);
}
虽然 //
是定义性检查而不是存在性检查,但环境变量未定义的唯一方法是未设置它。这归结为提到 %*ENV
和每个环境变量。
如果只有几个,那么我可能会到此为止,但下一个让我印象深刻的重复是属性名称只是环境变量名称的小写,所以我们可以消除它也有重复,但代价是稍微复杂一点:
method new() {
multi env(Str $envVar) {
$envVar.lc => %*ENV{$envVar} // die "environment variable $envVar must exist"
}
multi env(Str $envVar, $type) {
.key => $type(.value) given env($envVar)
}
return self.bless(
|env('CONFIG_STRING_A'),
|env('CONFIG_STRING_B'),
|env('CONFIG_FLAG_C', Bool),
);
}
现在 env
returns 一个 Pair
,并且 |
将它扁平化到参数列表中,就好像它是一个命名参数一样。
最后,"power tool" 方法是在 class:
之外写一个像这样的特征
multi trait_mod:<is>(Attribute $attr, :$from-env!) {
my $env-name = $attr.name.substr(2).uc;
$attr.set_build(-> | {
with %*ENV{$env-name} -> $value {
Any ~~ $attr.type ?? $value !! $attr.type()($value)
}
else {
die "environment variable $env-name must exist"
}
});
}
然后将class写成:
class Configuration {
has $.config_string_a is from-env;
has $.config_string_b is from-env;
has Bool $.config_flag_c is from-env;
}
编译时的特性运行,并且可以以多种方式操纵声明。此特征根据属性名称计算环境变量的名称(属性名称总是像 $!config_string_a
,因此是 substr
)。 set_build
设置将 运行 在创建 class 时初始化属性的代码。它通过了各种在我们的情况下并不重要的东西,所以我们忽略了 |
的参数。 with
就像 if defined
,所以这与之前的 //
相同。最后,Any ~~ $attr.type
检查询问参数是否以某种方式受到约束,如果是,则执行强制转换(通过使用值调用类型来完成)。
所以我在评论中提到了这一点,但我认为将其作为实际答案会很好。我认为这对于构建基于 Docker 的系统的任何人来说都是有用的功能,因此采用了 Jonanthan 的示例代码,添加了一些用于导出 Traits Elizabeth 向我展示的功能并制作了 Trait::Env
用法是:
use Trait::Env;
class Configuration {
has $.config_string_a is env;
has $.config-string-b is env(:required);
has Bool $.config-flag-c is env is default(True);
}
如果未找到,:required
标志将打开 die
。它与 is default
特性配合得很好。属性名称是大写的,并且 -
在检查 %*ENV
之前被替换为 _
。
我有几个计划的更改,让它抛出一个命名的异常,而不是死掉,并更好地处理布尔值。由于 %*ENV
是具有布尔值 False 的字符串,这有点痛苦。
我有一个 class Configuration
读取环境变量:
class Configuration {
has $.config_string_a;
has $.config_string_b;
has Bool $.config_flag_c;
method new() {
sub assertHasEnv(Str $envVar) {
die "environment variable $envVar must exist" unless %*ENV{$envVar}:exists;
}
assertHasEnv('CONFIG_STRING_A');
assertHasEnv('CONFIG_STRING_B');
assertHasEnv('CONFIG_FLAG_C');
return self.bless(
config_string_a => %*ENV{'CONFIG_STRING_A'},
config_string_b => %*ENV{'CONFIG_STRING_B'},
config_flag_c => Bool(%*ENV{'CONFIG_FLAG_C'}),
);
}
}
my $config = Configuration.new;
say $config.config_string_a;
say $config.config_string_b;
say $config.config_flag_c;
有没有更简洁的表达方式?例如,我在检查中重复环境变量名称和构造函数的 return 值
我可以很容易地看到编写另一个更通用的 class,它封装了配置参数的必要信息:
class ConfigurationParameter {
has $.name;
has $.envVarName;
has Bool $.required;
method new (:$name, :$envVarName, :$required = True) {
return self.bless(:$name, :$envVarName, :$required);
}
}
然后将这些滚动到 Configuration
class 中的列表中。但是,我不知道如何重构 Configuration
中的构造函数来适应这一点。
想到的最直接的变化是将 new
更改为:
method new() {
sub env(Str $envVar) {
%*ENV{$envVar} // die "environment variable $envVar must exist"
}
return self.bless(
config_string_a => env('CONFIG_STRING_A'),
config_string_b => env('CONFIG_STRING_B'),
config_flag_c => Bool(env('CONFIG_FLAG_C')),
);
}
虽然 //
是定义性检查而不是存在性检查,但环境变量未定义的唯一方法是未设置它。这归结为提到 %*ENV
和每个环境变量。
如果只有几个,那么我可能会到此为止,但下一个让我印象深刻的重复是属性名称只是环境变量名称的小写,所以我们可以消除它也有重复,但代价是稍微复杂一点:
method new() {
multi env(Str $envVar) {
$envVar.lc => %*ENV{$envVar} // die "environment variable $envVar must exist"
}
multi env(Str $envVar, $type) {
.key => $type(.value) given env($envVar)
}
return self.bless(
|env('CONFIG_STRING_A'),
|env('CONFIG_STRING_B'),
|env('CONFIG_FLAG_C', Bool),
);
}
现在 env
returns 一个 Pair
,并且 |
将它扁平化到参数列表中,就好像它是一个命名参数一样。
最后,"power tool" 方法是在 class:
之外写一个像这样的特征multi trait_mod:<is>(Attribute $attr, :$from-env!) {
my $env-name = $attr.name.substr(2).uc;
$attr.set_build(-> | {
with %*ENV{$env-name} -> $value {
Any ~~ $attr.type ?? $value !! $attr.type()($value)
}
else {
die "environment variable $env-name must exist"
}
});
}
然后将class写成:
class Configuration {
has $.config_string_a is from-env;
has $.config_string_b is from-env;
has Bool $.config_flag_c is from-env;
}
编译时的特性运行,并且可以以多种方式操纵声明。此特征根据属性名称计算环境变量的名称(属性名称总是像 $!config_string_a
,因此是 substr
)。 set_build
设置将 运行 在创建 class 时初始化属性的代码。它通过了各种在我们的情况下并不重要的东西,所以我们忽略了 |
的参数。 with
就像 if defined
,所以这与之前的 //
相同。最后,Any ~~ $attr.type
检查询问参数是否以某种方式受到约束,如果是,则执行强制转换(通过使用值调用类型来完成)。
所以我在评论中提到了这一点,但我认为将其作为实际答案会很好。我认为这对于构建基于 Docker 的系统的任何人来说都是有用的功能,因此采用了 Jonanthan 的示例代码,添加了一些用于导出 Traits Elizabeth 向我展示的功能并制作了 Trait::Env
用法是:
use Trait::Env;
class Configuration {
has $.config_string_a is env;
has $.config-string-b is env(:required);
has Bool $.config-flag-c is env is default(True);
}
如果未找到,:required
标志将打开 die
。它与 is default
特性配合得很好。属性名称是大写的,并且 -
在检查 %*ENV
之前被替换为 _
。
我有几个计划的更改,让它抛出一个命名的异常,而不是死掉,并更好地处理布尔值。由于 %*ENV
是具有布尔值 False 的字符串,这有点痛苦。