使用环境变量构建配置类的更简洁方法? [英] More concise way to build a configuration class using environment variables?

查看:47
本文介绍了使用环境变量构建配置类的更简洁方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个类 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆