如何将这种副作用转化为优雅的 Vapor 操作链?

How to transform this side-effect into an elegant chaining of Vapor operations?

我开始使用 Vapor 来 运行 和 API,但我想不出 优雅 的方式来执行以下操作:(1 ) 加载 Fluent 关系并 (2) 在 (3) 响应原始请求之前等待 HTTP 回调的响应。

在我将现实生活中的实体转化为行星和恒星以维护 Vapor 文档中的示例之后,这就是我最终编写的代码。它有效™️,但我无法实现良好的操作链☹️。

    func flagPlanetAsHumanFriendly(req: Request) throws -> EventLoopFuture<Planet> {
        Planet(req.parameters.get("planetID"), on: req.db)
            .unwrap(or: Abort(.notFound))
            .flatMap { planet in
                // I load the star because I need its ID for the HTTP callback
                _ = planet.$star.load(on: req.db).map {
                    let uri = URI(scheme: "http", host: "localhost", port: 4200, path: "/webhooks/star")
                    // HTTP Callback
                    req.client.post(uri) { req in
                        try req.content.encode(
                            WebhookDTO(
                                starId: planet.star.id!,
                                status: .humanFriendly,
                                planetId: planet.id!
                            ),
                            using: JSONEncoder()
                        )
                    }
                    .map { res in
                        debugPrint("\(res)")
                        return
                    }
                }

                // Couldn't find a way to wait for the response, so the HTTP callback is a side-effect
                // and its response is not used in the original HTTP response...
                // If the callback fails, my API can't report it. 
                planet.state = .humanFriendly
                return planet.save(on: req.db).map { planet }
            }
    }

问题#1。结合 2 EventLoopFuture

我的第一个问题是我找不到一种方法来加载父关系,同时将实体保留在范围内。

Planet(req.parameters.get("planetID"), on: req.db)
    .unwrap(or: Abort(.notFound))
    .flatMap { planet in // planet is in the scope 
        return planet.$star.load(on: req.db) // returns a EventLoopFuture<Void>
    }
    .map {
      // I know that star has been loaded but I lost my `planet` reference ☹️
      ???
    }

我假设有一个操作员应该能够 return 混合使用 2 个 EventLoopFuture 实例,但我想不通。

问题#2。将 EventLoopFuture 与辅助 HTTP 请求的响应链接

在这里,我假设我错过了一个操作员,它允许我在响应原始请求之前等待请求的响应,同时保持对行星的引用。


欢迎提供有关如何在良好的操作链中实现此目的的帮助——当然,如果可能的话——我将非常乐意相应地更新 Vapor 的文档。

简而言之,如果您需要一个未来的结果包含在另一个未来的结果中,那么您必须嵌套回调——您不能使用链接。例如:

Planet(req.parameters.get("planetID"), on: req.db)
    .unwrap(or: Abort(.notFound))
    .flatMap { planet in // planet is in the scope 
        return planet.$star.load(on: req.db).map {
            // Star is now eager loaded and you have access to planet
        }
    }

如果您有多个互不依赖的期货,但您需要两者的结果,您可以使用 and 将两者链接在一起并等待它们。例如:

func flagPlanetAsHumanFriendly(req: Request) throws -> EventLoopFuture<Planet> {
    Planet(req.parameters.get("planetID"), on: req.db)
        .unwrap(or: Abort(.notFound))
        .flatMap { planet in
            // I load the star because I need its ID for the HTTP callback
            planet.$star.load(on: req.db).map {
                let uri = URI(scheme: "http", host: "localhost", port: 4200, path: "/webhooks/star")
                // HTTP Callback
                let postFuture = req.client.post(uri) { req in
                    try req.content.encode(
                        WebhookDTO(
                            starId: planet.star.id!,
                            status: .humanFriendly,
                            planetId: planet.id!
                        ),
                        using: JSONEncoder()
                    )
                }.map { res in
                    debugPrint("\(res)")
                    return
                }

                planet.state = .humanFriendly
                let planetUpdateFuture = planet.save(on: req.db)

                return postFuture.and(planetUpdateFuture).flatMap { _, _ in
                    return planet
                }
            }
        }
}