Open leoadonia opened 3 weeks ago
环境变量不存在时, 目前有两种设置默认值的可选,null 或者 空字符串 (针对 env 来说, 值类型一般是 string).
设置为 null, 即在 property 的值在 runtime 内是以 ten_value_null 对象的形式存在.
设置为 null 的好处
目前看来, 主要是能够提供一种信息表达 env 不存在. 而且系统 (unix/windows) 的 获取环境变量的 API 中, 如果 env 不存在, 也是返回 NULL. 也可以说, ten_value_null 是与系统 API 对应起来.
即在开发者调用 get_property_string() 时, 会产生一个错误, 告知调用者该 env 不存在.
设置为 空字符串 的好处
对于 extension 开发者来说比较透明, 不用考虑系统 API 带来的类型差异的处理, 因为在 value 系统中, ten_value_null 和 ten_value_string 是两个不同类型的对象.
对于设置为 null:
通过 get_property_xx() 获取配置时, 能够提供一种方式告知调用者 env 不存在. 但这个 "告知" 的对象, 应该是设置 env 的, 而不应该是调用 get_property_xx() 的. 即是使用者期望通过 env 设置 property, 对于 extension 来说, 只是通过 get_property_xx() 获取配置, 至于配置是直接写的字符串, 还是从 env 中获取的, 应该不需要关心, 也没有这个先验的假设. 从这个角度来说, 如果要有在 env 不存在时有通知, 那被通知的对象也应该是填写 property 的用户. 所以, 应该是提供通知的方式, 如 system event 之类, 打印错误日志, 程序退出等. 或者认为将 property 设置为 value_null, 在 schema 校验或者 get_property_string 触发时产生的错误来 通知, 也可以. 主要的点应该是在 通知, 而不是从 value 本身考虑的默认值.
如果 env 不存在, 设置为 value_null, 那大概率是会产生错误的. 因为 env 的值类型是 string, 那使用者在使用 env 时, 应该就是预期 extension 是接收 string 类型的 property 的. 那么, 设置为 null, 在触发 get_property_string 时, 会产生错误. 既然会报错, 那应该是尽量早的暴露错误. 因为是 property 的设定, 无法在编译期报错, 那还是应该在解析 env 时就报错, 及早发现, 避免 extension 没有适当的错误处理而产生非预期的错误, 尤其是在运行时处理业务请求阶段.
扩展来看, env 的方式, 只是一种 "注入" property 的方式, 与现有的 value system 无关, 不应该跟 value 的 default value 产生关系.
注入 property 的方式可以有多种:
所以, env 只是一种获取值的渠道, 在获取到之后, 应用到 value system 中. 也可以理解为是在定义 property store.
那这样, 后续扩展其他渠道时, 可能会出现两种 "异常" (姑且将获取不到值认为是 异常):
如果结合 schema 来看, 还会存在 store 存的值与预期的不一致的问题, 如果是直接将从 store 中的值作用到 value 中, 也存在类型不匹配的错误.
从这个角度, env 中获取不到返回的 NULL, 就应该是一种统一的处理方式, 即如何在 获取阶段表达 not found 的错误.
从 value system 的角度来看, 处理 env 的方式, 应该算是 marshal / unmarshal 的一种实现. 可以是从 marshal / unmarshal 定义标准接口, 当传入的值是 NULL 时, 该如何 unmarshal.
Springboot 中提供 AutoConfiguration 的能力, 可以将不同的数据源作为配置注入到 Configuratio Pojo class 中. 如:
@Configuration
class Config {
@Value("${example.name}")
private String name;
}
上述是将配置源中 key 为 example.name
(example scope 下的 name) 的值注入到 Config
class 中的 name
中.
预期 example.name
需要定义在数据源中, 比如 Springboot 中的默认数据源 application.yaml
中.
application.yaml 中未定义指定的 key, 如:
spring:
application:
name: demo
example:
在启动时, 程序报错:
java.lang.IllegalArgumentException: Could not resolve placeholder 'example.name' in value "${example.name}"
application.yaml 中指定key的内容是从环境变量中获取, 但是环境变量不存在, 如:
spring:
application:
name: demo
example:
name: ${SOME_VARIABLE}
但运行环境中不存在环境变量 SOME_VARIABLE
, 则启动时报错:
java.lang.IllegalArgumentException: Could not resolve placeholder 'SOME_VARIABLE' in value "${SOME_VARIABLE}"
application.yaml 中指定 key 的内容从环境变量中获取, 同时指定了默认值; 但环境变量不存在, 如:
spring:
application:
name: demo
example:
name: ${SOME_VARIABLE:}
上述通过 :
的方式设置了默认值, 为空字符串. 启动正常, Config::name
的值为 空字符串.
application.yaml 中指定 key 的内容从环境变量中获取, 同时指定了默认值; 环境变量存在. 如:
export SOME_VARIABLE=1a2s
则正常启动, Config::name
的值为 1a2s
.
application.yaml 中指定 key 的内容从环境变量中获取, 同时指定默认值为 null
; 环境变量不存在. 如:
spring:
application:
name: demo
example:
name: ${SOME_VARIABLE:null}
则正常启动, Config::name
的值是 字符串 null
, 而不是 NULL
. 即 name == null
返回 false.
application.yaml
是 Springboot 的默认的本地化配置源, 可以基于 spring cloud config server 支持不同的 backend, 如 git. 但作用到 AutoConfiguration 上的原则是一致的.
与 os env 不同的是, Springboot 中的 variable name 允许是以 数字开头, 如:
spring:
application:
name: demo
example:
name: ${3A:}
是可以正常启动以及正常获取值的.
K8S 中的 ConfigMap 有类似的应用场景, 比如将 ConfigMap 中的某一个 field 注入到 container 的环境变量中 (即将 config source A 中的内容作为另一种 config container B 的源, 以另一种方式来获取值, 如不再是 GetConfig, 而是通过 GetEnv).
Key 不存在, Pod 启动失败
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: registry.k8s.io/busybox
command: [ "/bin/sh", "-c", "env" ]
env:
- name: SPECIAL_LEVEL_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.how
上述示例是从 ConfigMap special-config
中获取 special.how
的值, 作为 test-container
中环境变量 SPECIAL_LEVEL_KEY
的值.
如果 key
不在指定的 ConfigMap 中, 则 Pod 会启动失败. 是在启动 Pod 之前会做 validate, 会返回失败.
Key 声明为 optional
如果需要指定 key 是 optional 的, 需要显示声明, 如下:
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: gcr.io/google_containers/busybox
command: ["/bin/sh", "-c", "env"]
env:
- name: SPECIAL_LEVEL_KEY
valueFrom:
configMapKeyRef:
name: a-config
key: akey
optional: true # mark the variable as optional
如果 ConfigMap 不存在, 或者 key 不存在于 ConfigMap 中, Pod 可以启动, 同时环境变量 SPECIAL_LEVEL_KEY 是空字符串.
与 springboot 不同的是, key 的名称需要满足 env 的规范, 比如不能以数字开头. 如:
envFrom:
- configMapRef:
name: env-configmap
这样会将 ConfigMap 中的 data 作为 env, 如果 ConfigMap 中有如 1badkey
的 key 时, 这时 Pod 可以
启动, 但是会跳过非法的 key, 同时以 k8s events 的形式记录错误.
Extension might uses the environment variable as the value of property, ex:
If the environment variable does not exist, the ten_value_t bound to this property is TEN_TYPE_NULL.
PyUnicode_FromString
will be crashed if the value is NULL.It's better to set the default value to an empty string rather than NULL if the environment does not exist.