了解如何初始化 Vapor 4 存储库

Understanding how to initialize a Vapor 4 repository

我正在尝试使用存储库模式将一些代码从 Vapor 3 迁移到 Vapor 4。我已经阅读了 Vapor 4 文档中的 documentation of this specific pattern,我想我大部分都理解了。

但是,我没有得到的一件事是在 Application 扩展中设置存储库工厂的方式。文档中的示例显示了这一点:

extension Application {
    private struct UserRepositoryKey: StorageKey { 
        typealias Value = UserRepositoryFactory 
    }

    var users: UserRepositoryFactory {
        get {
            self.storage[UserRepositoryKey.self] ?? .init()
        }
        set {
            self.storage[UserRepositoryKey.self] = newValue
        }
    }
}

如果我正确地阅读了 getter 方法(我可能不是 - 我远不是 Swift 专家),UserRepositoryFactory 结构的新实例将在引用 app.users 时创建并返回。然而,那时 self.storage[UserRepositoryKey.self] 的内容似乎没有任何变化。因此,如果我碰巧连续两次访问 app.users,我将返回 2 个 不同的 个实例,并且 self.storage[UserRepositoryKey.self] 将保持设置为 nil.

按照文档中的其余示例代码,它似乎定义了工厂在配置应用程序时将使用的 make 函数:

app.users.use { req in
    DatabaseUserRepository(database: req.db)
}

这里似乎 app.users.use 会得到一个新的工厂实例并调用其 use 函数来为该实例设置适当的 make 方法。

稍后,当我去处理一个请求时,我使用了这个 Request 扩展定义的 request.users 方法:

extension Request {
    var users: UserRepository {
        self.application.users.make!(self)
    }
}

这里似乎 self.application.users.make 将在 self.application.users 引用的不同存储库工厂实例上调用。因此,它不会应用先前在配置应用程序时设置的工厂 make 方法。

那么我在这里错过了什么?

看起来文档有点过时了。您可以看看视图或客户端是如何完成的,但是您需要在某个地方调用 initialize() 来设置存储库。这是我的工作存储库的样子:

import Vapor

extension Application {
    struct Repositories {
        
        struct Provider {
            let run: (Application) -> ()
            
            public init(_ run: @escaping (Application) -> ()) {
                self.run = run
            }
        }
        
        final class Storage {
            var makeRepository: ((Application) -> APIRepository)?
            init() { }
        }
        
        struct Key: StorageKey {
            typealias Value = Storage
        }
        
        let application: Application
        
        var repository: APIRepository {
            guard let makeRepository = self.storage.makeRepository else {
                fatalError("No repository configured. Configure with app.repositories.use(...)")
            }
            return makeRepository(self.application)
        }
        
        func use(_ provider: Provider) {
            provider.run(self.application)
        }
        
        func use(_ makeRepository: @escaping (Application) -> APIRepository) {
            self.storage.makeRepository = makeRepository
        }
        
        func initialize() {
            self.application.storage[Key.self] = .init()
        }
        
        private var storage: Storage {
            if self.application.storage[Key.self] == nil {
                self.initialize()
            }
            return self.application.storage[Key.self]!
        }
    }
    
    var repositories: Repositories {
        .init(application: self)
    }
}

它会在第一次使用时自动初始化。请注意,APIRepository 是用于我的存储库的协议。 FluentRepository 是该协议的 Fluent 实现。然后像你一样我有一个关于请求的扩展以在请求处理程序中使用它:

extension Request {
    var repository: APIRepository {
        self.application.repositories.repository.for(self)
    }
}

最后,您需要配置它以使用正确的存储库。所以在我的 configure.swift 我有:

app.repositories.use { application in
    FluentRepository(database: application.db)
}

在测试中,我可以将它切换到不接触数据库的 in-memory 存储库:

application.repositories.use { _ in
    return inMemoryRepository
}

我已经设法从文档中获取示例 as-is。

通过调试器跟踪执行过程,如您所说,可以预测对 get 的调用,而这个 returns 来自 .init() 的实例作为故障转移先前存储的值。您引用的示例中包括:

struct UserRepositoryFactory {
    var make: ((Request) -> UserRepository)?
    mutating func use(_ make: @escaping ((Request) -> UserRepository)) {
        self.make = make
    }
}

接下来执行这个use函数,即mutating并更新变量make。我相信正是对 make 的更改触发了对 set 的调用。它肯定会在 use 之后和 configure.swift 继续执行之前立即发生。因此,当服务器正式启动并且您实际在路由中使用 Repository 时,有一个存储的实例可以根据需要重用。