Golang context.Context propagation

时间:2018-03-08 18:51:32

标签: go

我想问一下我们应该如何处理Golang中的上下文传播问题。

我的应用程序是HTTP JSON API服务器。

我会将上下文用作信息数据的容器(例如请求ID,我从请求或进程中解压缩的一些内容)。

最愚蠢的优势之一是用于统计和记录的车辆数据和标签。例如。能够在每个日志行添加我拥有的所有包中的事务ID

我面临的问题如下:

func handleActivityY(w http.ResponseWriter, r *http.Request) {
    info, err := decodeRequest(r)
    ...
    stepOne, err := stepOne(r.Context(), info)
    ...
    stepTwo, err := stepTwo(r.Context(), stepOne)
    ...
}

这种设计的问题在于上下文是一个不可变的实体(每次我们添加一些东西或者我们设置一个新的超时时,我们都有一个新的上下文。)

除了在每次函数调用时返回上下文(以及返回值,如果有的话和错误),都不能传播上下文。

实现这项工作的唯一方法是:

func handleActivityY(w http.ResponseWriter, r *http.Request) {
    ctx, info, err := decodeRequest(r)
    ...
    ctx, stepOne, err := stepOne(ctx, info)
    ...
    ctx, stepTwo, err := stepTwo(ctx, stepOne)
    ...
}

我已经污染了我的包中几乎所有带有context.Context参数的函数。除了其他参数之外,返回它似乎有点矫枉过正。

真的没有其他更优雅的方法吗?

我目前正在使用gin框架,它有自己的上下文,并且是可变的。我不想为Gin添加依赖项。

2 个答案:

答案 0 :(得分:3)

在上下文管道的早期,添加一个指向数据结构的可变指针:

type MyData struct {
    // whatever you need
}

var MyDataKey = /* something */

ctx, cancel := context.WithValue(context.Background(), MyDataKey, &MyData{})

然后在需要修改数据结构的方法中,只需这样做:

data := ctx.Value(MyDataKey)
data.Foo = /* something */

有关并发访问安全的所有常规规则都适用,因此如果多个goroutine可以同时读取/设置您的数据值,则可能需要使用互斥锁或其他保护机制。

答案 1 :(得分:1)

真的没有其他更优雅的方法吗?

stepOne可以独立于上下文返回它自己的数据,并与调用者如何使用其信息隔离(即将其放在数据库/上下文中并将其传递给其他函数)

func handleActivityY(w http.ResponseWriter, r *http.Request) {
    ctx, info, err := decodeRequest(r)
    ...
    stepOne, err := stepOne(ctx, info)
    ...
    ctx = ctx.WithValue(ctx, "someContextStepTwoNeeds", stepOne.Something())
    stepTwo, err := stepTwo(ctx, stepOne)
    ...
}

传递请求作用域的IMO数据应该是非常小的上下文信息