Skip to main content

Get the Reddit app

Scan this QR code to download the app now
Or check it out in the app stores
r/golang icon
r/golang icon
Go to golang
r/golang
A banner for the subreddit

Ask questions and post articles about the Go programming language and related tools, events etc.


Members Online

Question about managing external sessions

Hello. I'm fairly new to golang and I'm building an application for my team that consumes webhooks and then makes calls to various other tools to grab related data and action on them.

I can't recall where I "learned" this idea but currently I'm using a struct that I initialize when I create my cmd and then attach the various clients to the API client struct. The handlers, then utilize this struct on the method to access the various resources. The problem I'm having right now is that one of these clients has a session duration of 2 hours, and then it needs to be refreshed. However, I feel like the way that I'm initializing and using the client is a problem. So I'm looking for guidance on how I can improve this workflow or if I'm just doing something very anti-go.

In my `cmd` file:

logger := cmdutil.NewLogger("api")
defer logger.Sync()

awsC := aws.NewClient(logger)
cacheC := cmdutil.NewCache(awsC.AMS.RedisPassword, logger)
odinC := odin.NewClient(cacheC, awsC, logger).Login()

a := api.NewAPI(ctx, logger, cacheC, awsC, odinC)

NewAPI returns the following struct:

type api struct {
    aws        *aws.Client
    httpClient *http.Client
    logger     *zap.Logger
    cache      *cache.Cache
    odin       *odin.Client
}

Down stream in the handlers, I utilize a method to access this API struct as such:

func (a *api) WebhookHandler(w http.ResponseWriter, r *http.Request) {
    // truncated
    a.odin.GetAccountById(accountID);
    // truncated
}

At this point, I think I've made a design choice that is not great. In the `GetAccountById` function, I check session expiration and if needed make a call to refresh session however I'm unsure how I'm expected to update the initial client instance.

I'm not sure if for this use case, the concept of Globals is more appropriate or if there is another design pattern that makes more sense.

I'm looking for some guidance. If I haven't provided enough information, please let me know and I'm more than happy to share what I can.

Edit: Thinking about this more, this could still work if I split up my refresh logic from trying to return an entire client to just returning the necessary items and updating them but I still feel there is likely a better way.

Share
Sort by:
Best
Open comment sort options
u/nilpointr avatar

Why don't you wrap the client in a mechanism that will detect when you get a 401 (or whatever the signal your session expired is), refresh and then retry the original request?

Edited

I think that is what I want to do in my head but I'm not sure how to update the original client with all of the information required for any subsequent request to be validated. Would I just simply update the pieces on the pointer? Edit: I think I actually figured this one out. I'm testing it now.

More replies

Depending on the API, I've used two approaches:

  • Background refresh goroutine swapping out the client hourly

  • Detect the error you get when your session expires and refresh and retry at the time

Got it. Structure wise, is the way I'm initializing the initial struct and passing it down to all the methods common practice? I think this is ultimately more of where my confusion lies. I initially attempted to replace the original client but it seems you can't replace the whole thing. So I was looking at whether it made sense to initialize the client as a singleton. Alternatively, it seems like maybe I should just update the underlying attributes that are required to change for the session to now be valid.

The way you are doing it is quite common and generally considered best practice. It's called dependency injection. You'll need to change the value of the field in your API struct (using a mutex guard) to swap it, or you can introduce an interface and wrap it with a type that knows how to do the refreshing transparently to your API type. These days I'd do the latter, but the former is perfectly acceptable too.

More replies
More replies

The x oauth2 package achieves something similar with a “token source” that refreshes the access token, I would recommend taking a look https://pkg.go.dev/golang.org/x/oauth2

u/nilpointr avatar

+1. We aped this exact thing at my company to solve this problem.

More replies

Thank you all for the feedback. I was able to successfully implement a wrapper around the actual HTTP request that refreshes the token if it returns a 401.