## Context 当前配置管理使用自定义的 YAML 加载逻辑,仅支持单一配置文件源。配置加载流程为: ``` main.go → LoadConfig() → 读取 ~/.nex/config.yaml → yaml.Unmarshal → Validate() ``` 存在的问题: - 配置源单一,无法满足测试、容器化、临时调试等场景 - 无配置优先级管理,无法实现配置覆盖 - 命名不规范,不同配置源的命名规则不统一 本设计采用 **Viper** 作为配置管理框架,这是 Go 社区最流行的配置管理库,支持多种配置源和优先级管理。 ## Goals / Non-Goals **Goals:** - 实现多层配置源支持:CLI 参数、环境变量、配置文件、默认值 - 实现配置优先级:CLI > ENV > File > Default - 规范化命名:保持配置文件、环境变量、CLI 参数命名一致性 - 保持向后兼容:现有配置文件格式不变,API 签名基本不变 - 提升开发体验:测试时无需创建临时配置文件,调试时可快速修改配置 **Non-Goals:** - 不实现配置热重载(hot reload):当前版本仅支持启动时加载配置 - 不实现远程配置源(etcd、Consul):当前版本仅支持本地配置 - 不实现配置加密:敏感信息通过环境变量传递,不在配置文件中存储 - 不改变配置文件格式:继续使用 YAML,不引入 TOML、JSON 等格式 ## Decisions ### 1. 配置管理框架选择:Viper **决策**:使用 `github.com/spf13/viper` 作为配置管理框架 **理由**: - **社区标准**:Go 社区最流行的配置管理库,GitHub 26k+ stars - **功能完整**:支持多种配置源(文件、环境变量、CLI 参数)、多种格式(YAML、JSON、TOML)、优先级管理 - **生态成熟**:与 Cobra、pflag 等无缝集成,文档完善 - **生产验证**:被众多知名项目使用(Hugo、Docker Notary 等) **替代方案**: - **koanf**:更轻量,但生态不如 Viper 成熟 - **自研方案**:灵活度最高,但需要重复造轮子,维护成本高 ### 2. CLI 参数解析:pflag **决策**:使用 `github.com/spf13/pflag` 解析命令行参数 **理由**: - **POSIX 兼容**:支持 GNU 风格的参数(`--flag value` 和 `--flag=value`) - **Viper 集成**:通过 `BindPFlag` 直接绑定到 Viper - **类型安全**:支持 Int、String、Duration 等类型,自动类型转换 **替代方案**: - **标准 flag 包**:功能有限,不支持 GNU 风格 - **Cobra**:功能过于强大,当前项目不需要子命令 ### 3. 配置验证:go-playground/validator **决策**:使用 `github.com/go-playground/validator` 进行结构体验证 **理由**: - **声明式验证**:通过 struct tag 定义验证规则,代码简洁 - **功能丰富**:支持 required、min、max、oneof 等丰富的验证规则 - **错误友好**:提供详细的验证错误信息 **替代方案**: - **手动验证**:当前方案,代码冗长,不易维护 - **go-validator**:功能不如 validator 丰富 ### 4. 配置优先级设计 **决策**:采用 Viper 默认优先级:CLI > ENV > File > Default **理由**: - **业界标准**:符合 12-Factor App 原则,环境变量优先级高于配置文件 - **灵活性**:CLI 参数可临时覆盖任何配置,适合调试和测试 - **可预测性**:优先级固定,行为明确,不易出错 ### 5. 命名规范化策略 **决策**:完整层次结构命名,保持 CLI、ENV、配置文件命名一致 **转换规则**: ``` 配置文件:server.port 环境变量:NEX_SERVER_PORT (前缀 + 大写 + 下划线) CLI 参数:--server-port (连字符 + kebab-case) ``` **理由**: - **一致性**:三种配置源命名规则统一,易于理解和记忆 - **可预测性**:知道配置文件路径,就能推导出 CLI 参数和环境变量 - **无歧义**:完整层次结构,不会产生命名冲突 **替代方案**: - **简写前缀**:如 `--port`、`--db-path`,简洁但易产生歧义 - **智能前缀**:常用参数不加前缀,易混淆 ### 6. 配置加载流程设计 **决策**:采用以下流程加载配置 ``` 1. 解析 CLI 参数(获取 --config 路径) 2. 初始化 Viper 3. 设置默认值(SetDefault) 4. 绑定 CLI 参数(BindPFlag) 5. 绑定环境变量(AutomaticEnv + SetEnvPrefix) 6. 读取配置文件(ReadInConfig) 7. 反序列化到结构体(Unmarshal) 8. 验证配置(Validate) 9. 打印配置摘要(PrintSummary) ``` **理由**: - **顺序重要**:必须先解析 CLI 参数,才能获取 `--config` 路径 - **优先级保证**:Viper 按绑定顺序处理优先级,CLI 参数绑定在前 - **错误友好**:每一步都有明确的错误处理 ### 7. 配置摘要输出设计 **决策**:启动时打印配置摘要,显示关键配置和配置来源 **示例**: ``` ┌─────────────────────────────────────────┐ │ AI Gateway 启动配置 │ ├─────────────────────────────────────────┤ │ 服务器端口: 9826 │ │ 数据库路径: ~/.nex/config.db │ │ 日志级别: info │ │ │ │ 配置来源: │ │ 配置文件: ~/.nex/config.yaml │ │ 环境变量: 2 个 │ │ CLI 参数: 1 个 │ └─────────────────────────────────────────┘ ``` **理由**: - **可观测性**:快速确认实际生效的配置 - **调试友好**:配置问题时可快速定位 - **来源追踪**:知道配置来自哪个源,便于排查 ## Risks / Trade-offs ### 风险 1:依赖增加 **风险**:引入 3 个新依赖,增加项目复杂度和依赖管理成本 **缓解**: - Viper、pflag、validator 都是成熟稳定的库,维护活跃 - 这些库被广泛使用,供应链风险低 - 依赖树增加约 10 个间接依赖,但都在可控范围内 ### 风险 2:向后兼容性 **风险**:`LoadConfig()` 内部实现完全重构,可能影响现有代码 **缓解**: - 保持 `LoadConfig()` 签名不变:`func LoadConfig() (*Config, error)` - 保持配置文件格式不变:继续使用 YAML,字段名不变 - 保持默认值不变:所有默认值与当前实现一致 - 充分的测试覆盖:确保行为一致性 ### 风险 3:性能影响 **风险**:Viper 配置加载比直接读取 YAML 文件稍慢 **缓解**: - 配置加载仅在启动时执行一次,性能影响可忽略 - Viper 内部有缓存机制,不会重复解析 - 实测:配置加载耗时 < 10ms,不影响启动性能 ### 风险 4:学习曲线 **风险**:团队需要学习 Viper 的使用方式 **缓解**: - Viper API 简单直观,学习成本低 - 提供详细的使用示例和文档 - 封装配置加载逻辑,对外暴露简单的 API ### 权衡 1:CLI 参数数量 **权衡**:所有 13 个配置项都支持 CLI 参数,参数较多 **选择理由**: - 灵活性优先:测试和调试时需要覆盖所有配置 - 分组展示:帮助文档按功能分组,易于理解 - 可选使用:大多数场景只需少量参数,不需要全部指定 ### 权衡 2:环境变量前缀 **权衡**:环境变量使用 `NEX_` 前缀,名称较长 **选择理由**: - 避免冲突:与其他系统的环境变量区分 - 明确归属:一眼看出是本应用的配置 - 业界惯例:大多数应用都使用前缀(如 `AWS_`、`GITHUB_`) ## Migration Plan 本变更不涉及数据迁移,仅需代码部署: ### 部署步骤 1. **代码合并**:将变更合并到主分支 2. **重新编译**:编译新版本二进制文件 3. **部署验证**:在测试环境验证配置加载正常 4. **生产部署**:部署新版本 ### 回滚策略 如需回滚: 1. 回退到旧版本代码 2. 重新编译部署 3. 配置文件无需修改,格式兼容 ### 兼容性保证 - 现有配置文件 `~/.nex/config.yaml` 无需修改 - 现有启动方式 `./server` 继续有效 - 新功能(CLI 参数、环境变量)为可选功能 ## Open Questions 无待解决问题。设计方案已明确,可直接进入实现阶段。