Using application context as queries' parent context - go

I am currently passing application/main context from main.go to repository.go so that I can use it as "parent" context to go with query context. Is this valid/idiomatic usage or not? Also, shall I ditch the idea and just use context.Background() as "parent" context to go with query context instead?
main.go
package main
import (
"context"
"internal/user"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
repo := user.NewRepository(ctx, db)
// HTTP server is running.
}
internal/user/repository.go
package user
import (
"context"
"database/sql"
"time"
)
type Repository struct {
*sql.DB
}
var appCTX context.Context
func NewRepository(ctx context.Context, db *sql.DB) Repository {
appCTX = ctx
return Repository{db}
}
func (r Repository) Insert(args ...interface{}) error {
ctx, cancel := context.WithTimeout(appCTX, 5 * time.Millisecond)
defer cancel()
// Run query etc.
res, err := r.ExecContext(ctx, `INSERT INTO .....`, args...)
}

The idiomatic use of context is to pass it as the first function argument, and not store in structs. This is from the context doc:
Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx
So, even if you pass the main context down to your implementation, you should do that by passing the context to each operation.
Each self-contained operation (such as an HTTP request) should create a new context. If your main performs one such self-contained operation, you can pass the context down like this. However, if this is a server application, you should create a separate context for each request handler.

Related

Golang cli application - how to use context properly?

I'm new to golang and somewhat confused about context and how to use the context in golang applications.
Specifically im working on the cli application and just need to access mongo, for example.
Like - is this correct that I just create single shared ctx context variable, then use it for any operations that need context?
Would any operation that needs context restart the 5-second timer? or is this a shared timer?
package main
import (
"context"
"log"
"os"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
func main() {
log.SetOutput(os.Stdout)
// Create a context with a timeout of 5 seconds
//This defines a timeout context that will be canceled after 5 seconds.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// always defer in case func returns early
defer cancel()
//Creates a new ClientOptions instance.
clientOptions := options.Client()
clientOptions = clientOptions.ApplyURI("mongodb+srv://127.0.0.1?retryWrites=true&w=majority")
//Connect to mongo
client, err := mongo.Connect(ctx, clientOptions)
defer client.Disconnect(ctx)
if err != nil {
log.Fatal(err)
}
//Test connection to the database
log.Println("I: test mongo connection using ping")
err = client.Ping(ctx, readpref.Primary())
if err != nil {
log.Fatal(err)
}
log.Println("I: Fin")
}
If you think about it, it makes no sense that a context.Context could be shared "horizontally" (meaning between operations not part of the same call stack). A golang Context provides the context within which an operation (including any nested operations below it in the call stack) are to be performed - such as "within X seconds," to protect against hanging due to communications delays, etc. So if you issue 10 requests in parallel, you should give each one its own context - you probably don't want the tenth one to fail because the first one did. If you are just using a context.Background() or context.TODO(), without further decoration, you probably don't need to store the Context in a variable the first time you create it - you can just create when you pass it to the first function in the call stack, and properly constructed code will pass it down the stack as appropriate, applying necessary decorations along the way:
func Execute() {
DoThing(context.Background())
// Other stuff
}
func DoThing(pctx context.Context) {
ctx, cancel := context.WithTimeout(pctx, 10 * time.Second) // Timeout after 10 seconds
defer cancel()
DoThingThatMayTakeAWhile(ctx)
select {
// You may want other stuff here
case <-ctx.Done():
// Context timed out, maybe log an error?
}
}
func DoThingThatMayTakeAWhile(pctx context.Context) {
DoThingNeedingInfoInContext(context.WithValue(pctx, "thisisakey", "value"))
}
func DoThingNeedingInfoInContext(ctx context.Context) {
val := ctx.Value("thisisakey")
// Do something with val, check that it isn't nil, etc.
}
If I were to make multiple calls to DoThingThatMayTakeAWhile(), I'd want to give each one a separate child context - I would not want to share ctx with each of them.
So in your code, every call to mongo.Connect() should receive a freshly created context.Context instance.
You create a new context variable for each operations that need context.
The timer of a context will never restart.
In your example, try to add time.Sleep(6*time.Second) after context.WithTimeout, you will see all operations return error context deadline exceeded.

Beego - I need "context.Context" and not the Beego context

I am trying to write a function that will validate a Google id token.
The oauth2 package requires me to pass in the context when creating a new service, like this:
package services
import (
"context"
"google.golang.org/api/oauth2/v2"
)
func ValidateToken(ctx *context.Context, idToken string) {
// I need to pass context.Context in to the oauth2 library
oauth2Service, err := oauth2.NewService(*ctx)
tokenInfoCall := oauth2Service.Tokeninfo()
tokenInfoCall.IdToken(idToken)
tokenInfo, err := tokenInfoCall.Do()
In Beego this.Ctx is an instance of the Beego context module, so this code won't compile:
func (c *TokenController) Post(ctx *context.Context) {
requestParams := struct {
Google_id_token string
}{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestParams)
// Type mismatch
services.ValidateToken(c.Ctx, requestParams.Google_id_token)
How can I reach the context that I need to pass in to the OAuth2 library?
Edit: I'm working around it by passing in context.Background(), but I'm not sure that I fully understand the side effects of this. I'm pretty new to Golang and it feels like background context should only be used at "higher" levels?
func ValidateToken(idToken string) {
ctx := context.Background()
oauth2Service, err := oauth2.NewService(ctx)
try this : c.Ctx.Request.Context()
also don't use pointer in arg ctx in function ValidateToken because context.Context in stdlib is interface

Access DB instance from go-chi route handlers

I am trying to build a REST API with go-chi and Gorm.
I am not sure how I should pass the Gorm DB instance to the route handlers.
Or if I should create one instance per handler, which does not sound right to me.
Should I use middleware, dependency injection or other? What would be recommended pattern here?
package main
import (
"encoding/json"
"fmt"
"github.com/go-chi/chi/v5"
"log"
"net/http"
"os"
"time"
)
func main() {
r := chi.NewRouter()
r.Get("/", indexHandler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
log.Printf("Defaulting to port %s", port)
}
db := Connect()
migrations(db)
logStartServer(port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), r))
}
func logStartServer(port string) {
log.Printf("Listening on port %s", port)
log.Printf("Open http://localhost:%s in the browser", port)
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
//How can I access db here?
//result := db.Find(&users)
policy := InsurancePolicy{ValidFrom: time.Now()}
err := json.NewEncoder(w).Encode(policy)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
Use methods instead of functions. This allows you to pass any information needed by the handlers using the receiver of those methods:
type MyHandler struct {
DB *gorm.DB
}
func (m MyHandler) IndexHandler(w http.ResponseWriter, r *http.Request) {
// Use m.DB here
}
In main:
handler:=mypkg.MyHandler{DB:gormDB}
r.Get("/", handler.IndexHandler)
In some cases, a closure makes more sense.
func GetIndexHandler(db *gorm.DB) func(http.ResponseWriter,*http.Request) {
return func(w http.ResponseWriter,req *http.Request) {
// Implement index handler here, using db
}
}
func main() {
...
r.Get("/", GetIndexHandler(db))
Declaring the DB instance as a global variable is quite convenient if your project is small.
A number of ways for organising DB access are documented here quite well. Pick the one which fits your needs.
In the DB/query function itself. I personally make a separate package for controllers and a separate package for services. I handle all the request validation and HTTP stuff in the controller (which has my handler functions). Then, if everything checks out, I call a service package. The service package is the one that calls the DB as well as any other services or API integrations.
Yet, where ever you call the DB, usually you are calling into a db package that has a bunch of query functions with friendly names like db.GetAccountByID or something like that. Well, that db function is exactly where you pass the *sql.DB or *gorm.DB object.
An example would be...
package db
func GetAccountByID(id int, db *gorm.DB) (*model.Account, error) {
if db == nil {
db = conn // conn is the package level db connection object
}
//...
}
Generally, when the server starts, I create the DB connection (which functions as a connection pool) and so it's not really necessary to pass it into the function. So, why do it? Well, it's because of testing. You don't want your DB handler reaching out to a package level DB connection object because it becomes more difficult to do isolated testing of that function.
So, this function signature gives you that testability and the initial if condition still uses that single central DB connection object if nil is passed in for the DB value, which is always is nil unless you are testing.
This is just one approach but one I've used successfully for years now.

Context without cancel propagation

How can I create a copy (a clone if you will) of a Go context that contains all of the values stored in the original, but does not get canceled when the original does?
It does seem like a valid use case to me. Say I have an http request and its context is canceled after the response is returned to a client and I need to run an async task in the end of this request in a separate goroutine that will most likely outlive the parent context.
func Handler(ctx context.Context) (interface{}, error) {
result := doStuff(ctx)
newContext := howDoICloneYou(ctx)
go func() {
doSomethingElse(newContext)
}()
return result
}
Can anyone advice how this is supposed to be done?
Of course I can keep track of all the values that may be put into the context, create a new background ctx and then just iterate through every possible value and copy... But that seems tedious and is hard to manage in a large codebase.
Since context.Context is an interface, you can simply create your own implementation that is never canceled:
import (
"context"
"time"
)
type noCancel struct {
ctx context.Context
}
func (c noCancel) Deadline() (time.Time, bool) { return time.Time{}, false }
func (c noCancel) Done() <-chan struct{} { return nil }
func (c noCancel) Err() error { return nil }
func (c noCancel) Value(key interface{}) interface{} { return c.ctx.Value(key) }
// WithoutCancel returns a context that is never canceled.
func WithoutCancel(ctx context.Context) context.Context {
return noCancel{ctx: ctx}
}
Can anyone advice how this is supposed to be done?
Yes. Don't do it.
If you need a different context, e.g. for your asynchronous background task then create a new context. Your incoming context and the one of your background task are unrelated and thus you must not try to reuse the incoming one.
If the unrelated new context needs some data from the original: Copy what you need and add what's new.

The value in context can not transmitted in different package?

Today I try to program with context,code as follow:
package main
func main(){
ctx := context.Background()
ctx = context.WithValue(ctx,"appid","test111")
b.dosomething()
}
package b
func dosomething(ctx context.Context){
fmt.Println(ctx.Value("appid").(string))
}
Then my program has crashed.I think it's due to that these ctx is in different package
I suggest you to use context only in a lifetime of a single task and pass the same context through functions.
Also you should understand where to use context and where just to pass arguments to functions.
Another suggestion is to use custom types for setting and getting values from context.
According to all above, you program should look like this:
package main
import (
"context"
"fmt"
)
type KeyMsg string
func main() {
ctx := context.WithValue(context.Background(), KeyMsg("msg"), "hello")
DoSomething(ctx)
}
// DoSomething accepts context value, retrieves message by KeyMsg and prints it.
func DoSomething(ctx context.Context) {
msg, ok := ctx.Value(KeyMsg("msg")).(string)
if !ok {
return
}
fmt.Println("got msg:", msg)
}
You can move function DoSomething into another package and just call it as packagename.DoSomething it will change nothing.

Resources