12factorial 中文文档教程
12 Factorial
12 Factorial 是一个简单的库,用于从环境变量和 Consul 构建动态配置。
What does it do?
// myconfig.js
import cfg from '12factorial'
// Configs are described as plain javascript objects.
const spec = {
// they can contain any data you like
constantValue: 'abc-123',
someOtherValue: myFunction(),
// 12factorial.service will synchronise a field
// with a consul service, or with env vars.
database: cfg.service('my-db'),
credentials: {
// 12factorial.value will synchronise a field
// with consul's KV store, or with an env var.
username: cfg.value(),
password: cfg.value()
},
}
// 12factorial.build is the factory function that turns your
// object into a synchronised config object
cfg.build(spec).then((x) => doSomethingWithConfig(x))
Using values
value
函数将对象字段与标量值同步。 环境变量优先于存储在 Consul 中的值,并且可以提供默认值。 密钥和环境变量名称是按约定生成的。 可以使用默认值声明值。
{
// This can be set with the env var `VALUE` or the consul key `consul-prefix/value`
value: cfg.value(),
// this can be set with the env var `NESTED_OBJECT_VALUE` or the
// consul key `consul-prefix/nested/object/value`
nested: {
object: {
value: cfg.value()
}
},
// defaults to 'cheese' if no env var or consul key is available.
defaulted: cfg.value({ default: 'cheese' })
}
Namespacing environment variables
环境变量可以命名空间。 在下面的示例中,我们使用 myapp
的 envPrefix
。 此前缀将添加到变量名称中。
spec = {
nested: {
value: cfg.value()
}
}
process.env.MYAPP_NESTED_VALUE = 'beep'
cfg.build(spec, {envPrefix: 'myapp'}).then(config => {
console.log(config.nested.value) // prints beep
});
Syncing with consul
通过将 consul 配置传递给 12factor.build
,可以将值与 Consul 同步。 如果没有提供领事配置,我们将跳过领事同步。 只有“前缀”键是必需的,其他值默认为开发值。 值通过 Consul Watch 保持最新。
const consulConfig = {
prefix: 'myapp', // required.
host: '127.0.0.1', // defaults
port: 8500,
scheme: 'http'
}
const spec = {
nested: {
value: cfg.value()
}
}
cfg.build(spec, { consul: consulConfig }).then( config => {
console.log(config.nested.value) // prints the value of myapp/nested/value from consul kv.
});
Using Services
12factor.service
函数将对象键与服务的地址和端口同步。 与值一样,环境变量优先于 Consul 值。 环境变量名称按约定生成,并支持命名空间。
const spec = {
web: cfg.service('my-web-service'),
db: cfg.service('my-database')
}
process.env.MYAPP_WEB_ADDRESS = '127.0.0.1'
process.env.MYAPP_WEB_PORT = '3002'
const config = await cfg.build(spec, { envPrefix: 'myapp' })
console.log(config.web.getAddress()) // prints 127.0.0.1:3002
console.log(config.web.buildUri('/hello/world')) // prints 127.0.0.1:3002/hello/world
console.log(config.db.getAddress()) // prints the address + port of the 'my-database'
// service registered in Consul.
Using Services from Consul
服务自动从 Consul 同步。 默认情况下,我们使用“http://127.0.0.1:8500”作为我们的领事服务器地址。 Consul 的服务通过 Consul watch 保持最新。
如果为一个服务注册了多个地址,12factorial 将随机选择一个地址并一致地返回该地址,直到该服务在 Consul 中更新。
Mixing Values into Services
有时,为了便于使用,您可能希望将额外的值添加到服务对象中。 这通常用于存储带有服务地址的凭据。 此用例包含在 service
的 extend
方法中。
const spec = {
database: cfg.service('myapp-db').extend({
username: cfg.value(),
password: cfg.value({ sensitive: true })
})
}
const config = await cfg.build(spec)
console.log(config.database.getAddress())
console.log(config.database.username)
console.log(config.database.password)
Type Coercion
可以从字符串中自动强制转换值。 如果您设置默认值,我们将强制转换为与默认值相同的类型。 您可以通过传递读取器函数来覆盖对值的解析。
const spec = {
number: cfg.value({ default: 123 }).
bool: cfg.value({ default: true }),
custom: cfg.value({ reader: function (x) { return {msg: x } } })
}
process.env.NUMBER = "0xFF"
process.env.BOOL = FALSE // or false
process.env.CUSTOM = 'Hello World'
const config = await cfg.build(spec)
console.log(config.number) // 255
console.log(config.bool) // false
console.log(config.custom.msg) // hello world
Current Status
这是阿尔法质量。 缺少一些功能,并且几乎没有错误处理。 这是为了满足我自己对生产系统的要求,但不一定能满足你的要求。 随意玩耍并报告错误。
Roadmap
- [X] Add logging
- [ ] Make sure we handle errors properly
- [ ] Add type coercion for values
- [ ] Allow consul values to query using data center, tags etc.
- [ ] Extend env var opts to support an arbitrary variable name
- [ ] Support Hashicorp Vault for secrets.
- [ ] Consider supporting other back-ends
- [ ] Basic validity checks, eg. required fields.
- [ ] Reactivity, eg. raise an event when the database service updates so we can close connections etc
12 Factorial
12 Factorial is a simple lib for building dynamic configuration from environment variables and Consul.
What does it do?
// myconfig.js
import cfg from '12factorial'
// Configs are described as plain javascript objects.
const spec = {
// they can contain any data you like
constantValue: 'abc-123',
someOtherValue: myFunction(),
// 12factorial.service will synchronise a field
// with a consul service, or with env vars.
database: cfg.service('my-db'),
credentials: {
// 12factorial.value will synchronise a field
// with consul's KV store, or with an env var.
username: cfg.value(),
password: cfg.value()
},
}
// 12factorial.build is the factory function that turns your
// object into a synchronised config object
cfg.build(spec).then((x) => doSomethingWithConfig(x))
Using values
The value
function synchronises an object field with a scalar value. Env vars take precedence over values stored in Consul, and defaults can be provided. Keys and environment variable names are generated by convention. Values can be declared with a default.
{
// This can be set with the env var `VALUE` or the consul key `consul-prefix/value`
value: cfg.value(),
// this can be set with the env var `NESTED_OBJECT_VALUE` or the
// consul key `consul-prefix/nested/object/value`
nested: {
object: {
value: cfg.value()
}
},
// defaults to 'cheese' if no env var or consul key is available.
defaulted: cfg.value({ default: 'cheese' })
}
Namespacing environment variables
Environment variables can be namespaced. In the following example, we use an envPrefix
of myapp
. This prefix will be added to the variable names.
spec = {
nested: {
value: cfg.value()
}
}
process.env.MYAPP_NESTED_VALUE = 'beep'
cfg.build(spec, {envPrefix: 'myapp'}).then(config => {
console.log(config.nested.value) // prints beep
});
Syncing with consul
Values can be synchronised with Consul by passing consul configuration to 12factor.build
. If no consul config is provided, we will skip consul synchronisation. Only the 'prefix' key is required, the other values default to development values. Values are kept up to date with a Consul Watch.
const consulConfig = {
prefix: 'myapp', // required.
host: '127.0.0.1', // defaults
port: 8500,
scheme: 'http'
}
const spec = {
nested: {
value: cfg.value()
}
}
cfg.build(spec, { consul: consulConfig }).then( config => {
console.log(config.nested.value) // prints the value of myapp/nested/value from consul kv.
});
Using Services
The 12factor.service
function synchronises an object key with the address and port of a service. As with values, environment variables take precedence over Consul values. Environment variable names are generated by convention, and support namespacing.
const spec = {
web: cfg.service('my-web-service'),
db: cfg.service('my-database')
}
process.env.MYAPP_WEB_ADDRESS = '127.0.0.1'
process.env.MYAPP_WEB_PORT = '3002'
const config = await cfg.build(spec, { envPrefix: 'myapp' })
console.log(config.web.getAddress()) // prints 127.0.0.1:3002
console.log(config.web.buildUri('/hello/world')) // prints 127.0.0.1:3002/hello/world
console.log(config.db.getAddress()) // prints the address + port of the 'my-database'
// service registered in Consul.
Using Services from Consul
Services are automatically synchronised from Consul. By default, we use 'http://127.0.0.1:8500' as the address of our consul server. Services from Consul are kept up to date with a Consul watch.
If there are multiple addresses registered for a service, 12factorial will select an address at random and return that address consistently until the service is updated in Consul.
Mixing Values into Services
Occasionally, for ease of consumption, you might want to add extra values into a service object. This is typically useful for storing credentials with a service's address. This use case is covered by the extend
method of a service
.
const spec = {
database: cfg.service('myapp-db').extend({
username: cfg.value(),
password: cfg.value({ sensitive: true })
})
}
const config = await cfg.build(spec)
console.log(config.database.getAddress())
console.log(config.database.username)
console.log(config.database.password)
Type Coercion
Values can be automagically coerced from strings. If you set a default, we will coerce to the same type as the default value. You can override the parsing of your values by passing a reader function.
const spec = {
number: cfg.value({ default: 123 }).
bool: cfg.value({ default: true }),
custom: cfg.value({ reader: function (x) { return {msg: x } } })
}
process.env.NUMBER = "0xFF"
process.env.BOOL = FALSE // or false
process.env.CUSTOM = 'Hello World'
const config = await cfg.build(spec)
console.log(config.number) // 255
console.log(config.bool) // false
console.log(config.custom.msg) // hello world
Current Status
This is alpha-quality. There are some missing features, and little error handling. This is intended to meet my own requirements for a production system, but may not meet yours. Feel free to play around and report bugs.
Roadmap
- [X] Add logging
- [ ] Make sure we handle errors properly
- [ ] Add type coercion for values
- [ ] Allow consul values to query using data center, tags etc.
- [ ] Extend env var opts to support an arbitrary variable name
- [ ] Support Hashicorp Vault for secrets.
- [ ] Consider supporting other back-ends
- [ ] Basic validity checks, eg. required fields.
- [ ] Reactivity, eg. raise an event when the database service updates so we can close connections etc