【问题标题】:Understanding how to initialize a Vapor 4 repository了解如何初始化 Vapor 4 存储库
【发布时间】:2020-11-29 15:21:50
【问题描述】:

我正在尝试使用存储库模式将一些代码从 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 专家),当引用 app.users 时,将创建并返回 UserRepositoryFactory 结构的新实例。但是,当时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 方法。

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

【问题讨论】:

    标签: vapor vapor-fluent


    【解决方案1】:

    看起来文档有点过时了。您可以查看视图或客户端是如何完成的,但您需要在某个地方调用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 是该协议的流畅实现。然后像你一样,我在请求上有一个扩展,可以在请求处理程序中使用它:

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

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

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

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

    application.repositories.use { _ in
        return inMemoryRepository
    }
    

    【讨论】:

    • 谢谢@0xTim - 查看您更新后的示例,我还有几个其他问题: 1. 在您的Request 扩展中,您在APIRepository 类型上调用了一个名为for 的函数。该功能是所有存储库都应定义的特殊功能,还是您碰巧在此特定存储库接口中使用的功能? 2. 您定义了一个名为Providerstruct,然后在use 函数之一中引用它,但我无法弄清楚该概念的用途或使用方式。据我所知,您的示例中没有其他地方引用它。你能澄清一下吗?
    • for 允许您使用与请求相同的事件循环来创建存储库。这有助于避免在不同的事件循环之间跳转(并避免线程崩溃)。 Provider 用于允许您切换不同的实现,尽管您是对的,但这里不需要它。大多数 Vapor 存储库(来自 Vapor 本身)都使用它,所以它在这里是因为复制和粘贴!如果设置正确,它将允许您执行application.repositories.use(.inMemoryRepository),但在这种情况下,我希望访问内存存储库,以便可以引用它来添加数据等
    • 因此,如果我正确理解了这种方法,application.repositories.repository 返回的存储库实例在当时与事件循环无关。当您引用 request.repository 时,将检索该未关联的存储库实例,然后将其与请求中的事件循环相关联。对吗?
    • 几乎 - app.repositories.repository 返回与应用程序事件循环关联的存储库实例,req.repository 与请求的事件循环。如果您在请求生命周期之外启动应用程序时需要做一些事情,这很有用
    • 您需要创建自己的事件循环组并控制其生命周期。请参阅this example 了解如何执行此操作。然后,您可以使用该事件循环创建您的应用程序 - like so 并使用它创建您的内存存储库,然后正确设置您的应用程序。记得在每次测试结束时关闭事件循环
    【解决方案2】:

    我已经设法从原样工作的文档中获取示例。

    使用调试器跟踪执行,正如您所说,有对get 的可预测调用,这会从.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 时,就会有一个存储的实例根据需要被重用。

    【讨论】:

    • 谢谢@Nick - 这真的很有趣。我还注意到这段代码在我自己的测试中像宣传的那样工作,我只是不确定如何。在调用use 之后调用set 的事实可以解释结果——尽管我不确定为什么会这样。 Swift 中有什么东西会导致变异的struct 被设置回“拥有”它的对象吗?
    • @TimDean,谢谢。我想我已经在某处读到过这种行为,但现在找不到了。它使用set 机制来存储更改似乎是合乎逻辑的(可能会触发 willSet 和 didSet 一路走来)。你的问题是一个非常有趣的问题。我很惊讶它没有获得更多选票。
    猜你喜欢
    • 2016-07-30
    • 1970-01-01
    • 2017-09-12
    • 2017-05-11
    • 2016-04-12
    • 2019-03-22
    • 1970-01-01
    • 1970-01-01
    • 2011-07-06
    相关资源
    最近更新 更多