raku - More concise way to build a configuration class using environment variables?
问题描述
I have a class Configuration
that reads in environment variables:
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;
Is there a more concise way to express this? For example, I am repeating the environment variable name in the check and the return value of the constructor.
I could easily see writing another, more generic class that encapsulates the necessary info for a config parameter:
class ConfigurationParameter {
has $.name;
has $.envVarName;
has Bool $.required;
method new (:$name, :$envVarName, :$required = True) {
return self.bless(:$name, :$envVarName, :$required);
}
}
Then rolling these into a List in the Configuration
class. However, I don't know how to refactor the constructor in Configuration
to accommodate this.
解决方案
想到的最直接的变化是改变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
返回 a Pair
,并将|
其展平到参数列表中,就好像它是一个命名参数一样。
最后,“电动工具”的方法是在类之外写一个这样的特征:
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 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
设置将在创建类时运行以初始化属性的代码。这会传递各种在我们的情况下并不重要的东西,所以我们忽略|
. with
就像 一样,所以这if defined
与前面的方法相同//
。最后,Any ~~ $attr.type
检查询问参数是否以某种方式受到约束,如果是,则执行强制(通过调用具有值的类型来完成)。
推荐阅读
- javascript - 基于登录用户的条件查询
- scrollview - IOS中的侧抽屉滚动视图问题
- javascript - 覆盖整个函数
- xamarin - xamarin ios 应用程序仅以不链接行为运行。当其他链接器行为崩溃时
- python-3.x - 如何检查特定的 IP 地址属于 PySpark 数据帧中的哪个范围?
- python - 无法在 python 中导入 timezonefinder
- c++ - 如何将多个 QJsonObject 添加到 QJsonDocument - Qt
- vue.js - $router.push 刷新页面并丢失数据
- python - 选择器爬取无结果
- laravel - Laravel 策略 ID 混淆