我如何管理App Engine Go运行时上下文以避免App Engine锁定?

时间:2013-05-30 02:06:26

标签: google-app-engine go

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

我注意到几乎任何使用App Engine服务的操作(例如数据存储区,邮件甚至功能)都要求您传递appengine.Context的实例,该实例必须使用函数{{1}检索}}

在我为App Engine编写此应用程序时,我希望能够轻松快速地将其移动到其他平台(可能是一个不支持任何App Engine API的平台),如果我应该选择的话。 / p>

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

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

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

appengine.Context

然后,在我的请求处理程序中,我可以使用package context import ( "appengine" ) var Context appengine.Context 设置该变量,并且在直接使用App Engine服务的模块中,我可以通过访问context.Context = appengine.NewContext(r)来获取上下文。没有干预代码需要知道context.Context对象的存在。唯一的问题是"multiple requests may be handled concurrently by a given instance",这会导致竞争条件和此计划的意外行为。 (一个请求设置它,另一个设置它,第一个访问它并得到错误的appengine.Context对象。)

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

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

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

提前致谢!

5 个答案:

答案 0 :(得分:8)

这很棘手,因为你自我规定的范围规则(这是一个明智的规则)意味着没有传递Context实例,并且没有类似于Java ThreadLocal来实现同样以偷偷摸摸的方式结束。这真的是一件好事,真的。

Context将日志支持(简单)与Call结合到appengine服务(不容易)。我认为有十个需要Context的appengine函数。除了将所有这些包裹在自己的外观之外,我无法看到任何干净的解决方案。

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

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

答案 1 :(得分: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)
    ...
}

答案 2 :(得分:1)

特别是在数据存储的情况下,您应该能够在不同的请求中重用相同的appengine.Context。我自己没有尝试过这样做,但这就是数据存储的替代API goon的工作方式:

  

Goon以不同的方式与数据存储区包不同:它记住了appengine Context,它只需要在创建时指定一次

存储应该依赖于HTTP请求的事实听起来很荒谬。我不认为数据存储依赖于通常意义上的特定请求。最有可能的是,它需要识别特定的Google App Engine应用程序,该应用程序在请求请求时显然保持不变。我的推测是基于快速浏览google.golang.org/appengine startStopButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { System.out.println("onclick"); new Thread(new Runnable() { @Override public void run() { while (true) { gpsProgressView.setText(String.valueOf(Math.random())); } } },"UpdateThread").start(); } }); 的{​​{3}}。

您可以针对其他Google App Engine API执行类似的观察。因为所有细节可能特定于实现,并且我在实际应用程序中实际使用这些观察结果之前进行了更深入的研究。

答案 3 :(得分:1)

值得注意的是,Go团队推出了golang.org/x/net/context包。

稍后在托管虚拟机中提供了上下文,回购邮件为here。文档说明:

  

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

这意味着您可以根据appengine轻松地在开发环境中编写其他软件包。

特别是很容易包装像appengine/log这样的包(普通日志包装器example)。

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

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

有一篇关于Go博客herecontext个软件包的文章。我写过关于使用上下文here的文章。如果你决定使用带有上下文传递的处理程序,那么在一个地方创建所有requre的上下文是很好的。您可以使用非标准请求路由器(如github.com/orian/wctx

)来完成此操作

答案 4 :(得分: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)获取适当的上下文以传递给依赖项。