Home > OS >  Should I pass context.Context to underlying DB methods in Go?
Should I pass context.Context to underlying DB methods in Go?

Time:07-17

I use semi-code here just to show my intention of whats going on in code and not complicating things here in the question.

I have a main.go file that calls a method that connects to mongoDB database:

mStore := store.NewMongoStore()

In NewMongoStore I have context that client.Connect uses to connect to database:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

Now in main.go I pass the store to my router controller file this way:

routes.GenericRoute(router, mStore)

In GenericRoute I get the mStore and pass it to function handlers:

func GenericRoute(router *gin.Engine, mStore store.Store) {
    router.POST("/users", controllers.CreateUser(mStore))
}

Now in CreateUser I again create a context as below to insert document into MongoDB:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

insertedId, err := repo.CreateUser(ctx, newUser{"John", "Doe"})

Here I passed context to createUser to insert a new document.

As you see in some parts I have passed context and in some parts I did not. I really do not have any idea what I should do? What is the correct way to work with contexts? Should I always pass context around or it is totally ok to create new contexts like this to not pass context in method parameters.

What is the best practice for this kind of coding? And which one is better from performance point of view?

CodePudding user response:

Based on my experience, Context has two major use cases:

  1. Pass down information. For your question, you might want to generate a request_id for each request and passing it down to the lowest part of your code, and logging this request_id to do error tracing across the whole code base.
    1. This feature is not always useful, for example you want to initialise a MongoDB connection, but it's done during service start-up. At this time there's no meaningful context, a context.Background with timeouts should be good enough.
    2. Be cautious with mutating values retrieved from Context, this could cause concurrent access if you're passing the Context all around.
  2. Auto-cancellation and timeouts. These two features doesn't come from nothing, you need to tune your code to handle these informations from the Context. But most third-party and standard libraries with an Context parameter can handle these two features gracefully (e.g. database libraries, HTTP call libraries). With this feature you can auto reclaim resources once the Context invalidated.
    1. Sometime you'll want to stop this cascading behaviour, for example writing logs in the background goroutine, then you need to create a new context.Background() to avoid these writes get cancelled once upstream context cancelled. context.Background() also clears the information context so sometime you need to extract the context information from the upstream context, and manually append them to this new context.

It's a bit overkill to force a Context parameter to all functions, (there's no point to add Context to a simple greatestCommonDivisor function) but adding a Context parameter to anywhere you need it never hurts. Context has good enough performance, for your use case (HTTP server & database writing), it should not cause visible overhead to your service.

  • Related