Vapor3开发指南: 入门 - Services



  • Services

    Services 是一个用于 Vapor 的依赖注入(也叫做反向控制)框架。这个 services 框架允许你去注册、配置和初始化任何应用中可能需要的服务。

    Container

    大多数是通过容器来进行服务之间的交互的。一个容器是由以下构成的:

    • Services: 一系列已注册的服务
    • Config: 特定服务的配置
    • Environment: 应用当前环境(测试、生产环境等等)
    • Worker: 关联容器的 event loop

    Vapor使用的大多数容器是:

    • Application
    • Request
    • Response

    启动应用时你应该使用 Application 来作为容器去创建必要的服务。使用 RequestResponse 容器来创建响应 request 的服务(在路由闭包和控制器中)。

    Make

    创建服务很简单,只需要在容器上调用 .make(_:) 并传递你想要的类型,该类型通常是一个协议,比如:Client

    let client = try req.make(Client.self)
    

    如果你明确知道想要哪个服务,也可以直接指定具体的类型。

    let leaf = try req.make(LeafRenderer.self)
    print(leaf) /// Definitely a LeafRenderer
    
    let view = try req.make(ViewRenderer.self)
    print(view) /// ViewRenderer, might be a LeafRenderer
    

    提示

    尽可能的依赖于协议而不是具体类型。这将会更容易测试你的代码(可以很容易的切换具体实现),可以对代码进行解耦。

    Services

    Services 结构体包含了所有已注册过的服务(包括你自己或service提供者的),你将经常需要在 configure.swift 文件中注册配置你的服务。

    Instance

    你可以使用 .register(_:) 来注册初始化服务实例。

    /// Create an in-memory SQLite database
    let sqlite = SQLiteDatabase(storage: .memory)
    
    /// Register to sevices.
    services.register(sqlite)
    

    注册完一个服务后,可以通过 Container 来创建。

    let db = app.make(SQLiteDatabase.self)
    print(db) // SQLiteDatabase (the one we registered earlier)
    

    Protocol

    注册服务时,你也可以约束一个具体协议。你可能已经注意到 Vapor 是如何注册它的路由器的。

    /// Register routes to the router
    let router = EngineRouter.default()
    try routes(router)
    services.register(router, as: Router.self)
    

    因为使用了 as: Router.self 来注册 router,所以我们可以使用具体类型或协议来创建它。

    let router = app.make(Router.self)
    let engineRouter = app.make(EngineRouter.self)
    print(router) // Router (actually EngineRouter)
    print(engineRouter) // EngineRouter
    print(router === engineRouter) // true
    

    Environment

    Environment 用来动态改变 Vapor app 在特定场景下的行为。比如,应用部署后你可能想使用不同的用户名和密码用于数据库。使用 Environment 就可以很容易地管理起来。

    当从命令行运行 Vapor app 时,你可以传递 --env 参数来指定环境。默认情况下当前环境会是.development

    swift run Run --env prod
    

    上述例子中,我们以 .production 环境来运行 Vapor。该环境指定了 isRelease = true

    你可以使用传递到 configure.swift 中的 environment 来动态注册服务。

    let sqlite: SQLiteDatabase
    if env.isRelease {
        /// Create file-based SQLite db using $SQLITE_PATH from process env
        sqlite = try SQLiteDatabase(storage: .file(path: Environment.get("SQLITE_PATH")!))
    } else {
        /// Create an in-memory SQLite database
        sqlite = try SQLiteDatabase(storage: .memory)
    }
    services.register(sqlite)
    

    提示

    使用静态方法 Environment.get(_:) 来获取环境的字符串值。

    你也可以基于 environment 使用 .register(_:) 方法来动态注册服务。

    services.register { container -> BCryptConfig in
      let cost: Int
    
      switch container.environment {
      case .production: cost = 12
      default: cost = 4
      }
    
      return BCryptConfig(cost: cost)
    }
    

    Config

    如果多个服务都适用于给定的协议,你需要使用 Config 来声明你更想用哪个。

    ServiceError.ambiguity: Please choose which KeyedCache you prefer, multiple are available: MemoryKeyedCache, FluentCache<SQLiteDatabase>.
    

    这也可以在 configure.swift 中完成,只需要使用 config.prefer(_:for:) 方法。

    /// Declare preference for MemoryKeyedCache anytime a container is asked to create a KeyedCache
    config.prefer(MemoryKeyedCache.self, for: KeyedCache.self)
    
    /// ...
    
    /// Create a KeyedCache using the Request container
    let cache = req.make(KeyedCache.self)
    print(cache is MemoryKeyedCache) // true
    

Log in to reply