Using context in Go microservices
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.Context
that 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 Request
type 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 context
package 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.