Using context in Go microservices

Johan Sim Jian An
NE Digital
Published in
4 min readMar 8, 2021

We build fairprice.com.sg as distributed microservices rather than a big monolith. Microservices usually talk to other dependencies (external APIs, internal APIs or data store) to complete its operations. Microservices can also be serving HTTP or RPC request and it forms part of a bigger request block. Microservices could also be pulling information from message queues for different kinds of processing.

When the microservices are doing all these different operations, there are requirements that are common:

  • When should an operation stop?
  • Who is the user that initiate the operation?

While we are building these microservices, Go’s context package provides us with a nice building block that fulfill all these requirement.

context Package

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes. — Go official documentation

Let’s look at the signature of the context.Context interface to see what it provides.

By looking at the Deadline and Done method, we know when an operation should be stopped. By looking at the Err method, we know whether an particular operation has an error. By looking at the Value method, we know whether an operation carries any request bounded value.

Creating new context.Context

There are a few functions defined in the context package help us in creating a context.Contextthat we can use throughout our microservices.

context.Background

This method is mainly used in our main package when we are creating an application global context which is not bound to a request.

context.TODO

This is hardly used in our microservices as it is only meant to be used when we are unclear of which Context to be used.

context.WithCancel

This return a new copy of the parent with a cancel function. When the cancel function is called, the Done channel is close. Usually, the returned cancel function is called within the code block which calls this context.WithCancel function.

context.WithDeadline

This is very similar to context.WithCancel except that this also accept a time when this context should be considered done. If the context is not cancelled before that time, the Done channel will be closed.

context.WithTimeout

This is very similar to context.WithDeadline except that this accept a duration instead of a time.

Getting the context.Context

Compared to creating a new context.Context in our microservices, we often receives context.Context via the request itself. The net/http package contains a Requesttype which contains a private context field.

To get the private context from the Request, there is a Context method. To change the context, there is a WithContext method which returns us a shallow copy of Request.

A sample HTTP handler would normally looks like this.

As context contains the cancellation signal, anytime the connection is terminated (eg. client aborted connection), doA in this case can also exit earlier without doing any wasteful operation as client won’t be receiving the response.

Packages that works well with context.Context

Listed here are the common packages that we are using together with context with a short code snippets.

As contextpackage is only added in Go 1.7 and to maintain backward compatibility in Go 1.x, a lot of the packages added functions with Context suffix to indicate that these functions now accept context as the first parameter which often is named ctx.

net/http

database/sql

github.com/gomodule/redigo/redis

gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer

We wrap our HTTP router with Datadog provided middleware which allows us to monitor and trace HTTP requests and responses. After the HTTP server is instrumented correctly, you’ll be able to do these in your server.

What we have gotten from DataDog would then be things like this.

Caveats when using context.Context

We sometimes forget what context is used for especially when we are launching goroutine to process some data in background.

To fix this, we should also create a new background context as this goroutine shouldn’t share the same cancellation as the the HTTP handler.

Conclusion

context.Context is a powerful construct within microservices. To fully harness its usefulness, for all the functions within the request call paths, functions are accepting ctx Context.Context as first argument. In this way, all request bound values will be propagated in a consistent fashion.

--

--

Johan Sim Jian An
NE Digital

I am husband of Ginny, father of Esther and Ezra, son of Frankie and Carol and brother of Alice and Sunny. I am a Christian. I am constantly improving myself.