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

Tim*_*ean 5 vapor vapor-fluent

我正在尝试使用 Repository 模式将一些代码从 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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我正确阅读了 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)
}
Run Code Online (Sandbox Code Playgroud)

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

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

extension Request {
    var users: UserRepository {
        self.application.users.make!(self)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

0xT*_*Tim 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

extension Request {
    var repository: APIRepository {
        self.application.repositories.repository.for(self)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

app.repositories.use { application in
    FluentRepository(database: application.db)
}
Run Code Online (Sandbox Code Playgroud)

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

application.repositories.use { _ in
    return inMemoryRepository
}
Run Code Online (Sandbox Code Playgroud)