Ask questions and post articles about the Go programming language and related tools, events etc.
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.
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?
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.
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.
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
+1. We aped this exact thing at my company to solve this problem.
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.