5

我正在尝试使用存储库模式将一些代码从 Vapor 3 迁移到 Vapor 4。我已经阅读了Vapor 4文档中有关此特定模式的文档,并且我想我大部分都理解它。

然而,我没有得到的一件事是存储库工厂在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.users这个Request扩展定义的方法:

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

在这里,它似乎self.application.users.make会在由self.application.users. 因此,它不会应用之前在配置应用程序时设置的工厂的 make 方法。

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

4

2 回答 2

4

看起来文档有点过时了。您可以查看视图或客户端是如何完成的,但是您需要调用某个地方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
}
于 2020-08-10T16:32:48.667 回答
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在路由中使用时,就会有一个存储的实例根据需要被重用。

于 2020-08-10T17:06:06.237 回答