Go's 'context' library - more patterns and anti-patterns

There are a number of things you can do wrong with go’s context.

Anti-Patterns

Passing required arguments a long way

Sometimes, you have a function which is very deep into the call stack, and it needs an ID or something which you know is available at a higher level. Resist the temptation to just throw it in the context: this hides what should be an explicit, required parameter.

It’s not a bad idea to unit test that your functions work when passed context.Background() for their context arguments.

Holding contexts around too long

As the context becomes required widely by a lot of functions, there is a temptation to just “hold onto one”. You should be very careful when moving context objects from lexical scope to objects which persist longer than the lifetime of the function which made them. If you built all of your logging and other cross-cutting concerns around the assumption that the context is valid, then this can throw maintainers off.

Storing values under regular string/integer keys

This might be safe a lot of the time, but context is not a simple string map. Part of the convention is that your keys are private to your model. Sticking to this will stop people from thinking they can treat it as a public store.

Good Patterns

The series is already quite long, but I believe the following use cases to be good applications of context:

Database Transaction Objects

Queries will usually run inside or outside a transaction, and a prepared statement object can be used on the transaction or outside with a simple wrapper like this:

    func ApplyContextTx(ctx context.Context, stmt *sql.Stmt) *sql.Stmt {
	    if tx, ok := TxFromContext(ctx); ok {
		    return tx.Stmt(stmt)
	    }
	    return stmt
    }

This has the property that it still works with an empty context, so the presence of a database transaction could be considered a cross-cutting concern.

Object Caches

Along with holding the database transaction, you might want to consider the merits of caching all objects read during the current open transaction, and dumping when the transaction commits or rolls back.

Again, it can work with an empty context by loading from the database directly. The caller doesn’t need to care that this magic is happening inside of context.

Side effect buffers

Say your application is writing events to a messaging queue when a transaction completes successfully. This is a good application of context. If there is no open transaction, the messages should be sent immediately. Otherwise they buffer and are canceled on rollback or sent on commit.

Share Comments
comments powered by Disqus