使用环境变量构建配置类的更简洁方法? [英] More concise way to build a configuration class using environment variables?
问题描述
我有一个类 Configuration
读取环境变量:
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;
有没有更简洁的表达方式?比如我在check中重复了环境变量名和构造函数的返回值.
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);
}
}
然后将这些滚动到 Configuration
类中的列表中.但是,我不知道如何重构 Configuration
中的构造函数以适应这一点.
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
改为:
The most immediate change that comes to mind is to change new
to be:
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
以及每个环境变量.
While //
is a definedness check rather than an existence one, the only way an environment variable will be undefined is if it isn't set. That gets down to one mention of %*ENV
and also of each environment variable.
如果只有几个,那么我可能会停在那里,但让我印象深刻的下一个重复是属性的名称只是环境变量名称的小写,因此我们也可以消除这种重复,以稍微复杂一点为代价:
If there's only a few, then I'd likely stop there, but the next bit of repetition that strikes me is the names of the attributes are just lowercase of the names of the environment variables, so we could eliminate that duplication too, at the cost of a little more complexity:
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
返回一个 Pair
,并且 |
将它压平到参数列表中,就好像它是一个命名参数一样.
Now env
returns a Pair
, and |
flattens it in to the argument list as if it's a named argument.
最后,强力工具"方法是在类之外编写这样的特征:
Finally, the "power tool" approach is to write a trait like this outside of the 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 Configuration {
has $.config_string_a is from-env;
has $.config_string_b is from-env;
has Bool $.config_flag_c is from-env;
}
Traits 在编译时运行,并且可以以各种方式操作声明.这个 trait 根据属性名称计算环境变量的名称(属性名称总是像 $!config_string_a
,因此是 substr
).set_build
设置将在创建类时运行以初始化属性的代码.它传递了在我们的情况下不重要的各种东西,因此我们忽略带有 |
的参数.with
就像 if defined
一样,所以这与前面的 //
方法相同.最后,Any ~~ $attr.type
检查询问参数是否以某种方式受到约束,如果是,则执行强制转换(通过调用具有值的类型来完成).
Traits run at compile time, and can manipulate a declaration in various ways. This trait calculates the name of the environment variable based on the attribute name (attribute names are always like $!config_string_a
, thus the substr
). The set_build
sets the code that will be run to initialize the attribute when the class is created. That gets passed various things that in our situation aren't important, so we ignore the arguments with |
. The with
is just like if defined
, so this is the same approach as the //
earlier. Finally, the Any ~~ $attr.type
check asks if the parameter is constrained in some way, and if it is, performs a coercion (done by invoking the type with the value).
这篇关于使用环境变量构建配置类的更简洁方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!