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?
2
Answers
I reached to an interesting answer to my own question, so I prefer to put here for future users if having the same question in mind.
If I pass the SAME context that I have connected to Mongo with to
userController
and pass it down further toCreateUser
function:NOTE: instead of cancelling context in
NewMongoStore
function Idefer cancel()
it inmain
function.After 10 seconds if you call
POST /users
you will getcontext deadline exceeded
, so basically you cannot use this context to do other stuff and you have to create new context on eachCreateUser
call.So what I have written is fine. I wait for 10 seconds to connect to
mongo
in my example and 1 second for my insert operation context.Based on my experience,
Context
has two major use cases:request_id
for each request and passing it down to the lowest part of your code, and logging thisrequest_id
to do error tracing across the whole code base.context.Background
with timeouts should be good enough.Context
, this could cause concurrent access if you’re passing theContext
all around.Context
. But most third-party and standard libraries with anContext
parameter can handle these two features gracefully (e.g. database libraries, HTTP call libraries). With this feature you can auto reclaim resources once theContext
invalidated.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 addContext
to a simplegreatestCommonDivisor
function) but adding aContext
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.