Skip to content

选项配置

选项配置(Options)是一种类型安全、统一、规范的应用程序配置方式,适用于多架构、多租户场景。它是依赖注入的重要扩展,帮助你将配置与业务逻辑解耦,提高代码的可维护性和使用的便利性。统一模块开发者和使用者之间的开发和使用习惯,简化模块上手的复杂度和可配置性。

快速启动

只需 5 步即可开始使用选项配置:

cangjie
import spire_extensions_injection.*

// 1. 定义选项类型
public class ApplicationOptions {
    var name = ""
}

main() {
    // 2. 创建服务集合
    let services = ServiceCollection()

    // 3. 注册选项配置
    services.configure<ApplicationOptions>({
        configureOptions => configureOptions.name = "spire"
    })

    // 4. 构建服务提供者
    let provider = services.build()

    // 5. 解析并使用选项
    let options = provider.getOrThrow<IOptions<ApplicationOptions>>()
    options.value.name |> println // spire
}

配置选项

命名选项

支持多租户或多配置场景,可以为不同名称注册不同配置:

cangjie
import spire_extensions_injection.*

main() {
    // 1. 创建服务集合
    let services = ServiceCollection()

    // 2. 注册多租户选项配置(默认、tenant1、tenant2)
    services.configure<ApplicationOptions>({
        configureOptions => configureOptions.name = "default"
    })
    services.configure<ApplicationOptions>("tenant1", {
        configureOptions => configureOptions.name = "tenant1"
    })
    services.configure<ApplicationOptions>("tenant2", {
        configureOptions => configureOptions.name = "tenant2"
    })

    // 3. 构建服务提供者并获取选项监控器
    let provider = services.build()
    let optionsMonitor = provider.getOrThrow<IOptionsMonitor<ApplicationOptions>>()

    // 4. 分别获取并打印不同租户的选项值
    optionsMonitor.currentValue.name |> println // default
    optionsMonitor.get("tenant1").name |> println // tenant1
    optionsMonitor.get("tenant2").name |> println // tenant2
}

注意

命名选项通过解析 IOptionsMonitor<TOptions> 来读取

配置后置处理(configureAfter)

有时需要在所有 configure 配置完成后再进行一次统一处理(例如你需要修改框架的既有配置),可以使用 configureAfter 进行配置覆盖:

cangjie
services.configureAfter<ApplicationOptions>({configureOptions => 
    configureOptions.name = "cangjie"
})

services.configure<ApplicationOptions>({configureOptions =>
    configureOptions.name = "spire"
})
// ...
let options = provider.getOrThrow<IOptions<ApplicationOptions>>()
options.value.name |> println // cangjie

批量配置(configureAll)

对所有命名的选项统一生效,适合做全局默认或统一收尾:

cangjie
import spire_extensions_injection.*

public class ApplicationOptions { var name = "" }

main() {
    let services = ServiceCollection()

    // 为不同名称先注册各自配置
    services.configure<ApplicationOptions>("tenant1", { o => o.name = "t1" })
    services.configure<ApplicationOptions>("tenant2", { o => o.name = "t2" })

    // 对所有命名统一设置(后置阶段)
    services.configureAll<ApplicationOptions>({ options => options.name = "all" })

    // 全局最终覆盖(后置阶段)
    services.configureAfterAll<ApplicationOptions>({ options => options.name = "after-all" })

    let provider = services.build()
    let monitor = provider.getOrThrow<IOptionsMonitor<ApplicationOptions>>()
    monitor.get("tenant1").name |> println // after-all
    monitor.get("tenant2").name |> println // after-all
}

顺序

所有 configure 先执行,随后执行所有 configureAfterconfigureAllconfigureAfterAll 在当前实现中都属于“后置阶段”,统一作用于所有命名。

配置校验

配置校验(validate)

配置完成后,可以调用 validate 方法对配置项进行校验,校验失败将会抛出异常

cangjie
import spire_extensions_injection.*

class TestOptions { var version: Int32 = 0 }

main() {
    let services = ServiceCollection()

    services.addOptions<TestOptions>("tenant1")
        .configure { configureOptions => configureOptions.version = 1 }
        .validate { options => return options.version > 1 }// 校验配置版本是否大于1

    let provider = services.build()
    let monitor = provider.getOrThrow<IOptionsMonitor<TestOptions>>()

    try {
        // 获取配置时抛出异常
        let _ = monitor.get("tenant1")
        println("An error should be catched here!")
    } catch (ex: OptionsValidationException) {
        println("Catch error success, validate success!")
    } catch (ex: Exception) {
        println("Catch error fails, validate fails!")
    }
    // output: Catch error success, validate success!
}

集成依赖注入

选项配置与依赖注入结合,可以轻松实现配置驱动的服务,下面是一个消息队列处理器的场景:

cangjie
import std.time.*
import spire_extensions_injection.*

// 定义选项类型,默认延迟为10秒
public class WorkServiceOptions {
    var delay = 10
}

// 定义服务类,通过构造函数注入选项
public class WorkService {
    // 在服务类的构造函数中声明 IOptions<T> 参数,框架会自动将选项对象注入
    public WorkService(let options: IOptions<WorkServiceOptions>){} 

    // 服务逻辑
    public func working() {
        while (!Thread.currentThread.hasPendingCancellation) {
            print("WorkService is working\n")
            sleep(Duration.second * options.value.delay)
        }
        print("WorkService working end")
    }
}

// 扩展方法,简化服务注册
extend ServiceCollection {
    public func addWorkService() {
        this.addOptions<WorkServiceOptions>()
        this.addSingleton<WorkService, WorkService>()
    }
}

main() {
    let services = ServiceCollection()
    services.addWorkService()

    // 后配置修改默认延迟为1秒
    services.configureAfter<WorkServiceOptions>{options => 
        options.delay = 1
    } 

    let provider = services.build()

    // 解析选项并获取服务
    let work = provider.getOrThrow<WorkService>()
    work.working()
}

总结

通过 addOptions<T>() 方法注册选项类型,然后服务可以通过构造函数注入 IOptions<T> 来获取配置,同时应用后配置修改默认延迟为 1s

实现了服务逻辑与配置解耦

常见用法

  • 类型安全:所有选项均为强类型,避免魔法字符串。
  • 依赖注入集成:选项对象可直接注入到服务中。
  • 后置处理:通过 configureAfter 实现统一收尾配置。
  • 命名选项:支持多租户/多环境配置。

最佳实践

  • 推荐将所有选项类型集中定义,便于管理和维护。
  • 配置逻辑建议拆分为多个 configure,最后用 configureAfter 统一收尾。
  • 命名选项适合多租户、分环境等复杂场景。
  • 选项对象建议只读,避免运行时被修改。