Context.Context, Dependencies and Avoiding Package Globals

Hello Friends!

I am looking for a bit of advice on a pattern that seems to have crept into most of my go projects. Here is the pruned down version:

package main

import (
	"fmt"
	"net/http"

	"github.com/gorilla/mux"

	"golang.org/x/net/context"
)

type User struct {
	Name string
}

type Project struct {
	Name string
}

type UserRepo interface {
	FindByName(string) *User
}

type ProjectRepo interface {
	FindByUser(*User) []*Project
}

type fakeUserRepo struct{}

func (f fakeUserRepo) FindByName(name string) *User {
	return &User{name}
}

type fakeProjectRepo struct{}

func (f fakeProjectRepo) FindByUser(user *User) []*Project {
	if user.Name == "bob" {
		return []*Project{}
	}

	return []*Project{&Project{"Project 1"}}
}

type Container struct {
	Users    UserRepo
	Projects ProjectRepo
}

type Handler func(context.Context, http.ResponseWriter, *http.Request)

func (c Container) Handler(handler Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := context.Background()

		if r.FormValue("name") != "" {
			user := c.Users.FindByName(r.FormValue("name"))
			ctx = context.WithValue(ctx, "user", user)
		}

		handler(ctx, w, r)
	})
}

type Index struct {
	Container
}

func (i Index) Show(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	user := userFromContext(ctx)
	if user == nil {
		w.Write([]byte(fmt.Sprintf("Please Login")))
	} else {
		projects := i.Projects.FindByUser(user)
		w.Write([]byte(fmt.Sprintf("Hello %s, you have %d projects", user.Name, len(projects))))
	}
}

func userFromContext(ctx context.Context) *User {
	user, ok := ctx.Value("user").(*User)
	if !ok {
		return nil
	}

	return user
}

func main() {
	container := Container{fakeUserRepo{}, fakeProjectRepo{}}
	index := Index{container}

	router := mux.NewRouter()
	router.Handle("/", index.Handler(index.Show))
	http.ListenAndServe(":3000", router)
}

My understanding of the context package is that all request scoped variables like current user should be attached to a context.Context. Is this one of the intended uses of context.Context? In other words, am I doing it right?

The second part of this pattern is how to avoid the globals. My idea here is to create a struct Container to house my application’s dependencies. I have two very trivial implementations in the example, but each one normally implements database access or other goop like http apis (I would also pass the context into these interfaces if needed).

My question for the second part, is there a better way to pass around instances of these interfaces? Would simply having a package level variable like database.Users be better? Or should I attach them to context.Context?

Thanks,
Benjamin

1 Like

I’d suggest only using the context when you really don’t have an option - like when you need to propagate some implicit information across package boundaries. That includes things unrelated to your business logic, such as load-balancing information, security or authentication data.

Putting business logic in the context makes things very hard to debug if anything bad happens. Also, it obviously loses type safety. It’s very easy to create “holes” in your application - where a function expects some information, but it wasn’t added properly to the context.

For your business logic, I’d propagate all data using the smallest possible set of data, ideally through function parameters.

If index.Show needs the user information, pass that as a function parameter to Show. And if the list of arguments grows too large, put them in a nice struct that is specific to Show.

I noticed you have a onion of layers HTTP handlers. That’s probably what’s driving your need for putting data in the context in the first place. Ideally, for large applications with many reusable components, I’d aim for a clear separation of the HTTP layer and the application logic. HTTP handlers are too loose and generic. It’s better to build systems with clear parameters/responses at every layer. They are easier to reason about and easier to refactor.

About the global variables: they can be OK. If they are really semantically global (say, you only have one Users repo), it’s OK to use package variables. Your “Container” struct is an even better approach - although I’d aim for a more descriptive name, since all structs are containers of data, in a way. I’ve seen folks calling this “Server”, which also make sense.

TL;DR use proper methods with the specific data you need and that returns the needed data. That way you don’t have to abuse the context.

1 Like

I really appreciate the well thought out response.

I’ll stick to your suggestion and use an application specific context and function parameters. It was I was doing before… then I saw something about using context somewhere and thought that this was an interesting use case. I’m glad I asked before I committed to it in my current project.

The onion layers of http handlers always seems to pop into my apps because 9/10 times there is a bunch of stuff that I want to run on every request. Mostly things like figuring out if the user is authenticated, grab the user from the db, load their preferences… the application specific boring stuff that most handlers and view need access to. I’ll play around with trying to remove that layer and see how things turn out.

Thanks

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.