使用环境变量构建配置类的更简洁方法?

时间:2018-08-19 05:25:06

标签: perl6

我有一个类import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { FormsModule } from '@angular/forms'; import { ParentComponent } from './parent/parent.component'; import { ChildComponent } from './child/child.component'; @NgModule({ declarations: [ AppComponent, ParentComponent, ChildComponent, ], imports: [ BrowserModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } ,它读取环境变量:

fmt.Println(math.MaxUint32)

4294967295

有没有更简洁的表达方式?例如,我在检查和构造函数的返回值中重复环境变量名称。

我很容易看到编写另一个更通用的类,该类封装了config参数的必要信息:

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; 类中的列表中。但是,我不知道如何重构class ConfigurationParameter { has $.name; has $.envVarName; has Bool $.required; method new (:$name, :$envVarName, :$required = True) { return self.bless(:$name, :$envVarName, :$required); } } 中的构造函数以适应这种情况。

2 个答案:

答案 0 :(得分:9)

想到的最直接的更改是将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返回一个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设置创建类时将运行以初始化属性的代码。在我们的情况下,各种事情变得不重要了,因此我们忽略了|的参数。 withif defined一样,因此这与之前的//相同。最后,Any ~~ $attr.type检查询问参数是否以某种方式受到约束,如果是,则执行强制转换(通过使用值调用类型来完成)。

答案 1 :(得分:4)

因此,我在评论中提到了这一点,但我认为将其作为实际答案会很好。我认为这对于构建基于Docker的系统的任何人都是有用的功能,因此以Jonanthan的示例代码为例,并添加了一些用于导出Traits的功能,伊丽莎白向我展示了并制作了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

我有几个计划的更改,使它抛出一个命名的Exception而不是死掉并更好地处理Boolean。因为%*ENV是具有布尔False的字符串,所以有点痛苦。