十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Encore 环境配置指南:使用 CUE 为不同环境定义应用行为

Encore 环境配置指南:使用 CUE 为不同环境定义应用行为 Encore 环境配置指南使用 CUE 为不同环境定义应用行为【免费下载链接】encoreThe infrastructure platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/encor/encore导读本文是 Encore 后端应用的环境化配置Configuration实战指南。通过 Encore 的encore.dev/config包与 CUE 配置语言你可以在一个代码库中为生产、开发、临时ephemeral、测试等不同环境定义差异化的应用行为——例如只在某个老生产环境开启只读模式、在测试中禁用邮件外发。读完本文你将掌握config.Load[T]()的使用约束、config.Value[T]包装器的运行期语义、et.SetCfg的测试级覆盖技巧以及 CUE 默认值、校验、条件分支等进阶模式。概述配置即同一份代码多套行为Encore 的配置系统解决的核心问题是默认行为 环境级覆盖。配置文件让你为应用定义默认行为再针对特定环境生产、开发、临时预览、测试进行覆盖从而在不影响其他环境部署的前提下做出调整。Encore 的配置文件使用 CUE 编写——CUE 是 JSON 的超集在保持 JSON 数据形状的同时额外提供了类型系统与表达式能力。相对纯 JSONCUE 增加了C 风格注释//与/* */无特殊字符的字段名可以省略引号字段末尾的逗号可选列表最后一个元素后允许存在逗号文件最外层花括号可省略支持表达式插值interpolation、推导comprehensions与条件conditionals重要提示敏感数据密钥、令牌、凭据请使用 Encore 的密钥管理功能而不是配置。配置中的值最终会以明文 JSON 形式注入运行环境见下文运行时机制不适合承载机密。使用 Config加载与生成基本加载模式在服务内部调用config.Load[*SomeConfigType]()即可加载配置。该调用必须在包级别package level进行不能在函数内部调用。原因是encore.dev/config包在编译期即由 Encore 编译器识别并静态生成对应的 CUE 定义见 pkgfn.go 中的Load签名与注释。package mysvc import ( encore.dev/config ) type SomeConfigType struct { ReadOnly config.Bool // 将系统置于只读模式 Example config.String } var cfg *SomeConfigType config.Load[*SomeConfigType]()这里传入类型参数的 struct 类型会触发 Encore 在服务目录下生成一个encore.gen.cue文件。该文件包含两部分内容你的配置类型的 CUE 定义以及 Encore 在运行时提供给服务的元数据#Meta。你可以依据当前运行环境在 CUE 文件中改写配置的最终取值。服务目录或子目录中所有以.cue结尾的文件都会被 Encore 加载交给 CUE 进行统一unify与求值最终计算出一份配置。以下是生成文件与手写配置的示例-- mysvc/encore.gen.cue -- // Code generated by encore. DO NOT EDIT. package mysvc #Meta: { APIBaseURL: string Environment: { Name: string Type: production | development | ephemeral | test Cloud: aws | gcp | encore | local } } #Config: { ReadOnly: bool // 将系统置于只读模式 Example: string } #Config -- mysvc/myconfig.cue -- // 将 Example 设置为 hello world Example: hello world // 默认不处于只读模式 ReadOnly: bool | *false // 但在旧的 production 环境中开启只读模式 if #Meta.Environment.Name old-prod { ReadOnly: true }说明配置加载仅支持在服务中进行加载出的数据不能从该服务以外的包引用否则会触发 ErrCrossServiceConfigUse 这类编译错误。加载机制的底层实现从源码结构看config.Load[T]()的运行时实现位于 runtimes/go/config/pkgfn.go通过getComputedCUE从环境变量ENCORE_CFG_服务名服务名大写读取 base64 编码的 JSON 配置——见 manager_internal.go 中的envName实现用 jsoniter 迭代器按生成的Unmarshaler[T]反序列化到 Go 类型。也就是说构建时 CUE 求值的结果会在运行时以 JSON 注入环境变量运行时读取后完成 Go 结构体还原。这也是为什么配置文件里不应存放密钥——它在运行环境中是明文可解码的。配置类型的编译期校验Encore 编译器会在构建阶段校验config.Load[T]()的类型与调用方式见 v2/parser/infra/config/config.go 的walkCfgToVerifyconfig.Load不接受任何参数且必须传入一个命名结构体类型errInvalidLoad、errInvalidConfigType配置结构体不允许非导出字段与匿名anonymous字段config.Value不能嵌套config.ValueerrNestedValueUsage字段类型必须是内建类型、内联结构体或命名结构体类型只能在服务顶层包中调用ErrConfigUsedInSubPackage只能在服务内引用ErrCrossServiceConfigUse。这些错误定义位于 v2/parser/infra/config/errors.go编译失败时 Encore 会给出精确的诊断信息。CUE Tags 在 Go 结构体中的应用你可以在 Go 结构体字段上使用cue标签为配置补充额外的约束。例如type FooBar { A int cue:100 B int cue:A-50 // 若设置了 AB 可被 CUE 推导 C int cue:AB // 进而 C 也可由 CUE 推导 } var _ config.Load[*FooBar]()Encore 会据此生成如下的 CUE 类型定义生成逻辑见 v2/codegen/cuegen/generator.go 的UserFacing它基于对encore.dev/config.Load[T]()的资源扫描config.Load生成 CUE#Config: { A: int 100 B: int A-50 // 若设置了 AB 可被 CUE 推导 C: int AB // 进而 C 也可由 CUE 推导 }100这类约束会参与 CUE 的最终统一求值——配置文件中给 A 赋值为 50 会导致构建期校验失败而 B、C 在未被显式赋值时可由 CUE 根据约束自动推导出具体值。Config Wrappersconfig.Value[T]与config.Values[T]Encore 为配置提供了包装类型config.Value[T]与config.Values[T]它们分别展开为函数类型func() T与func() []T见 runtimes/go/config/types.go。为什么是函数而非普通值因为底层值可能在应用运行期间发生变化——单元测试可以通过et.SetCfg提供覆盖值来测试不同行为。以函数形式访问每次读取都能拿到当前生效的值。-- svc/svc.go -- type mysvc import ( encore.dev/config ) type Server struct { // 包装器不必位于顶层结构体中 Enabled config.Bool Port config.Int } type SvcConfig struct { GameServerPorts config.Values[Server] } var cfg config.Load[*SvcConfig]() func startServers() { for _, server : range cfg.GameServerPorts() { if server.Enabled() { go startServer(server.Port()) } } } func startServer(port int) { // ... } -- svc/servers.cue -- GameServerPorts: [ { Enabled: false Port: 12345 }, { Enabled: true Port: 1337 }, ]任何可用于 API 请求/响应类型的类型都可以作为包装器的类型参数。为方便使用Encore 内置了以下别名均定义在 runtimes/go/config/types.goconfig.String、config.Bool、config.Int、config.Uint、config.Int8、config.Int16、config.Int32、config.Int64、config.Uint8、config.Uint16、config.Uint32、config.Uint64、config.Float32、config.Float64、config.Bytes、config.Time、config.UUID从实现看每个包装值在运行时都会被分配一个唯一的ValueIDnextID原子递增并由CreateValue/CreateValueList生成闭包GetMetaForValue通过请求追踪reqtrack精确定位当前 goroutine 正在读取哪个值的 ID 与路径见 helpers_internal.go从而支撑测试覆盖与未来的运行期更新能力。官方计划在未来支持运行中应用的配置实时更新今天使用包装器即为代码做好前瞻——届时可自动利用该能力无需改动代码。提供的元数据Provided Meta Values应用运行时Encore 会向 CUE 文件提供当前环境的信息这些字段位于生成的encore.gen.cue的#Meta中对应的运行时数据来源可参见 runtimes/go/appruntime/exported/config/config.go 的Runtime结构其中包含EnvName、EnvType、EnvCloud等字段APIBaseURLEncore API 的基础 URL可用于向应用发起 API 调用Environment包含应用运行环境信息的结构体Name环境名称例如old-prod、stagingTypeproduction、development、ephemeral或test之一Cloud应用运行的云平台为aws、gcp、encore或local之一以下是几种常用的条件判断// 因 encore run 而运行的应用本地开发 if #Meta.Environment.Type development #Meta.Environment.Cloud local {} // 运行在云端开发环境的应用 if #Meta.Environment.Type development #Meta.Environment.Cloud ! local {} // 运行在生产环境的应用 if #Meta.Environment.Type production {} // 运行在 Encore 为 GitHub 上开放的 Pull Request 创建的临时环境中的应用 if #Meta.Environment.Type ephemeral {}ephemeral环境即 Encore 为 PR 预览自动创建的临时环境——你可以借此在合并前安全地验证配置变更。使用 Config 进行测试通过元数据应用配置在测试中可以有不同于运行时的取值。例如在test类型环境下禁用对外副作用如给真实客户群发邮件-- config.cue -- // 默认发送邮件 SendEmails: bool | *true // 但在所有测试中禁用邮件 if #Meta.Environment.Type test { SendEmails: false } -- signup.go -- import ( context encore.dev/config ) type Config struct { SendEmails config.Bool } var cfg config.Load[Config]() //encore:api public func Signup(ctx context.Context, p *SignupParams) error { user : createUser(p) if cfg.SendEmails() { SendWelcomeEmail(user) } return nil }但有时你需要在单个测试内精细控制某个配置值例如专门测试开启欢迎邮件的路径。这时用#Meta就无法提供足够细的粒度Encore 提供了辅助函数et.SetCfg-- signup_test.go -- import ( errors testing encore.dev/et ) func TestSignup(t *testing.T) { err : Signup(context.Background(), SignupParams { ... }) if err ! nil { // 此处不应出错 t.Fatal(err) } if emailWasSent() { // 测试环境已禁用邮件不应发送 t.Fatal(email was sent) } } func TestSignup_TestEmails(t *testing.T) { // 本测试中开启欢迎邮件以便验证其发送逻辑 et.SetCfg(cfg.SendEmails, true) err : Signup(context.Background(), SignupParams { ... }) if err ! nil { // 此处不应出错 t.Fatal(err) } // 校验邮件确实被发送 if !emailWasSent() { t.Fatal(email was not sent) } }et.SetCfg的实现细节从源码看runtimes/go/et/config.goet.SetCfg的行为非常明确仅能在test类型环境中调用否则直接 panicet: cannot set config in non-test environment不支持覆盖 slice 与 array传入会 panic通过config.SetValueForTest写入覆盖值。覆盖值按*testing.T维度存储testOverrides map[*testing.T]map[ValueID]any见 runtimes/go/config/manager_internal.go读取时testOverrideOrValue会向上遍历测试的父级链查找覆盖——因此父测试的覆盖对其子测试同样生效而其他测试不受任何影响见 test_internal.go。et.SetCfg的名称即测试环境et encore test专用配置。实用的 CUE 模式若你初次接触 CUE官方CUE 文档与cuetorials值得一读。以下是几个立即可用的模式。默认值DefaultsCUE 支持默认值概念当没有其他具体值时使用默认值。默认值以*前缀标记// ReadOnlyMode 是布尔值未提供时默认为 false ReadOnlyMode: bool | *false if #Meta.Environment.Name old-prod { // 在该环境开启只读模式 ReadOnlyMode: true }CUE 内的校验Validation within CUE以_开头的字段不会被导出到求值后的具体配置可用于存放中间值。因为 CUE 允许同一字段被多次定义只要取值能统一可以借此构建复杂的校验逻辑import ( list // 导入 CUE 的 list 包 ) // 设置端口号默认仅 8080 // 开发环境则包含 8443 portNumbers: [...int] | *[8080] if #Meta.Environment.Type development { portNumbers: [8080, 8443] } // 端口号必须是数组且所有值都是 1024 的整数 portNumbers: [...int 1024] // 若端口列表包含 8080则视为有效 _portsAreValid: list.Contains(portNumbers, 8080) // 将值约束为 trueCUE 在值为 false 时即端口列表不含 8080报告错误 _portsAreValid: trueSwitch 语句Switch StatementsCUE 的if没有else分支复杂条件可以借助数组模拟 switch——取第一个匹配条件的值。下例会为SendEmailsFrom求出一个字符串SendEmailsFrom: [ // 这些相当于各个 case 分支 if #Meta.Environment.Type production { noreplyexample.com }, if #Meta.Environment.Name staging { stagingexample.com }, // 最后这个无条件值相当于 default 分支 dev-systemexample.dev, ][0] // 返回第一个匹配条件的值将 Map 键用作值Using Map Keys as ValuesCUE 允许提取 map 的键并作为值使用从而简化配置、减少重复// 定义要使用的类型 #Server: { server: string port: int 1024 enabled: bool | *true } // 声明 servers 为 字符串 - #Server 的映射 // 将键绑定到变量 Name servers: [Namestring]: #Server { // 将键与 server 字段的值联合 server: Name } servers: { Foo: { port: 8080 }, Bar: { port: 8081 enabled: false }, }求值后的具体配置为{ servers: { Foo: { server: Foo, port: 8080, enabled: true }, Bar: { server: Bar, port: 8081, enabled: false } } }关键源码索引配置运行时加载config.Load、getComputedCUE、环境变量注入机制——runtimes/go/config/pkgfn.go、runtimes/go/config/manager_internal.go包装器与内建别名定义Value[T]、Values[T]、config.String等——runtimes/go/config/types.go测试覆盖实现SetValueForTest、testOverrideOrValue与父测试链查找——runtimes/go/config/test_internal.go测试专用 APIet.SetCfg的环境与类型限制——runtimes/go/et/config.go编译期校验walkCfgToVerify与全部错误定义——v2/parser/infra/config/config.go、v2/parser/infra/config/errors.goencore.gen.cue生成器基于config.Load资源扫描生成 CUE 定义——v2/codegen/cuegen/generator.go运行时环境元数据来源Runtime.EnvName/EnvType/EnvCloud——runtimes/go/appruntime/exported/config/config.go小结Encore 的配置系统将Go 类型安全、CUE 声明式约束与环境感知三者结合config.Load[T]()在编译期校验并生成encore.gen.cue#Meta元数据让配置随环境自适应config.Value[T]包装器 et.SetCfg让单测可以精确控制配置行为。对于生产实践建议记住三点敏感数据走密钥管理而非配置把环境差异沉淀为#Meta条件与默认值为未来配置热更新提前采用包装器类型。【免费下载链接】encoreThe infrastructure platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/encor/encore创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表