19

我正在编写一个在 App Engine 的 Go 运行时上运行的 Go 应用程序。

我注意到几乎所有使用 App Engine 服务(例如 Datastore、Mail 甚至 Capabilities)的操作都需要您向它传递一个appengine.Context必须使用 function 检索的实例appengine.NewContext(req *http.Request) Context

当我为 App Engine 编写这个应用程序时,如果我愿意的话,我希望能够轻松快速地将它移动到其他平台(可能是不支持任何 App Engine API 的平台)。

因此,我通过围绕任何 App-Engine 特定交互(包括请求处理函数)编写小包装器来抽象出与 App Engine 服务和 API 的实际交互。使用这种方法,如果我确实希望迁移到不同的平台,我只需重写那些将我的应用程序与 App Engine 绑定的特定模块。简单明了。

唯一的问题是那个appengine.Context对象。我无法将它从我的请求处理程序通过我的逻辑层传递到处理这些 API 的模块,而无需将我的所有代码都绑定到 App Engine。我可以传递可以从中派生对象http.Requestappengine.Context对象,但这需要耦合可能不应该耦合的事物。(我认为最好的做法是让我的应用程序都不知道它是一个 Web 应用程序,除了那些专门用于处理 HTTP 请求的部分。)

想到的第一个解决方案是在某个模块中创建一个持久变量。像这样的东西:

package context

import (
    "appengine"
)

var Context appengine.Context

然后,在我的请求处理程序中,我可以使用和在直接使用 App Engine 服务的模块中设置该变量context.Context = appengine.NewContext(r),我可以通过访问来获取上下文context.Context。没有任何干预代码需要知道appengine.Context对象的存在。唯一的问题是“给定实例可能同时处理多个请求”,这可能会导致此计划的竞争条件和意外行为。(一个请求设置它,另一个设置它,第一个访问它并获取错误的appengine.Context对象。)

理论上我可以将数据存储appengine.Context到数据存储区,但是我必须将一些特定于请求的标识符向下传递到逻辑层到特定于服务的模块,以识别appengine.Context数据存储区中的哪个对象是当前请求的对象,这将再次结合事物我认为不应该耦合。(而且,它会增加我的应用程序的数据存储使用量。)

我还可以将appengine.Context对象传递到整个逻辑链中,interface{}并让任何不需要appengine.Context对象的模块忽略它。这将避免将我的大部分应用程序与任何特定的. 然而,这似乎也很混乱。

所以,我有点茫然如何干净地确保需要该appengine.Context对象的 App-Engine 特定模块可以获取它。希望你们能给我一个我还没有想到的解决方案。

提前致谢!

4

5 回答 5

8

这很棘手,因为您的自我强加的范围规则(这是一个明智的规则)意味着不传递Context实例,并且没有任何类似于 Java 的东西ThreadLocal可以通过偷偷摸摸的方式达到相同的目的。这实际上是一件好事,真的。

Context将日志支持(简单)与应用Call引擎服务(不容易)结合起来。我认为有十个 appengine 功能需要一个Context. 除了将所有这些包裹在您自己的门面后面之外,我看不到任何干净的解决方案。

有一件事可以帮助你——你可以在你的应用程序中包含一个配置文件,使用某种标志来指示它是否在 GAE 中。您的全局布尔值只需要存储此标志(而不是共享上下文)。然后,您的外观函数可以在决定是使用NewContext(r)来获取Context访问 GAE 服务还是使用相似结构来访问您自己的替代服务时参考此标志。

编辑:作为最后的评论,当你解决这个问题时,我可以邀请你分享你是如何做到的,甚至可能是一个开源项目?我厚颜无耻地问,但如果你不问...... ;-)

于 2013-05-30T20:22:55.837 回答
7

我(希望)通过像这样包装我的请求处理程序(在本例中称为“realHandler”)解决了这个问题:

http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ds := NewDataStore(r)
    realHandler(w, r, ds)
})

NewDataStore 创建一个 DataStore,它是一个抽象 GAE 数据存储的简单包装器。它有一个未公开的字段,用于存储上下文:

type DataStore struct {
    c appengine.Context
}

func NewDataStore(req *http.Request) *DataStore {
    return &DataStore{appengine.NewContext(req)}
}

在请求处理程序中,我可以在需要时访问抽象数据存储,而不必担心已经设置的 GAE 上下文:

func realHandler(w http.ResponseWriter, req *http.Request, db *DataStore) {
    var s SomeStruct{}
    key, err := db.Add("Structs", &s)
    ...
}
于 2014-01-12T14:57:46.560 回答
1

特别是在 Datastore 的情况下,您应该能够appengine.Context在不同的请求中重用相同的内容。我自己没有尝试过,但这是 Datastore 的替代 API 称为goon 的工作方式:

Goon 在很多方面与数据存储包不同:它记住 appengine 上下文,它只需要在创建时指定一次

顺便说一句,存储应该依赖于 HTTP 请求的事实听起来很荒谬。我不认为 Datastore 依赖于通常意义上的特定请求。最有可能的是,需要识别一个特定的 Google App Engine 应用程序,该应用程序在请求之间显然保持不变。我的推测是基于快速浏览google.golang.org/appengine.

您可以对其他 Google App Engine API 进行类似的观察。当然,所有细节都可能是特定于实现的,在实际应用中实际使用这些观察之前,我已经进行了更深入的研究。

于 2015-09-26T17:01:39.273 回答
1

值得注意的是,Go 团队引入了一个golang.org/x/net/context包。

后来在托管虚拟机中提供了上下文,repo 在这里。该文档指出:

此存储库支持 App Engine 上的 Go 运行时,包括经典 App Engine 和托管 VM。它提供用于与 App Engine 服务交互的 API。其规范的导入路径是google.golang.org/appengine.

这意味着您可以根据appengine.

appengine/log尤其是像(琐碎的日志包装器示例)这样的包变得非常容易包装。

但更重要的是,这允许人们以一种形式创建处理程序:

func CoolHandler(context.Context, http.ResponseWriter, *http.Request)

这里有一篇关于contextGo 博客上的包的文章。我在这里写了关于使用上下文的文章。如果您决定使用处理程序来传递上下文,那么最好在一个地方为所有请求创建上下文。您可以使用非标准请求路由器(如github.com/orian/wctx)来实现

于 2015-11-05T13:00:32.890 回答
0

我通过封装appengine.NewContext一个接口来处理这个问题,并通过不同的包提供不同的实现。这样,我不必将 GAE 链接到任何不使用它的二进制文件中:

type Context interface {
  GetHTTPContext(r *http.Request) context.Context
}

我为子包提供了一种在导入副作用时注册自己的方法,有点database/sql风格:

var _context Context
func Register(c Context) {
  _context = c // Nil checking, double registration checking omitted for brevity
}

我从一个普通的、非 GAE 二进制文件的默认实现开始,它只是抓取现有的上下文:

var _context Context = &defaultContext{} // use this by default
type defaultContext struct {}
func (d *defaultContext) GetHTTPContext(r *http.Request) context.Context {
  return r.Context()
}

然后我将 App Engine 实现放在一个包中mything/context/appengine

import(
  ctx "mything/context"
)

type aecontext struct {}
func (a *aecontext) GetHTTPContext(r *http.Request) context.Context {
  return appengine.NewContext(r)
}

func init() {
  ctx.Register(&aecontext{})
}

然后我的 GAE 二进制文件可以拉入子包,该子包在以下位置注册init

import(
  _ "mything/context/appengine"
)

我的应用程序代码用于GetHTTPContext(r)获取适当的上下文以传递给依赖项。

于 2018-08-27T15:55:08.103 回答