Dependency injection in gorillamux handlers

826 views
Skip to first unread message

Nathanael Curin

unread,
Sep 23, 2019, 4:50:58 AM9/23/19
to golang-nuts
Hi everyone,

I'm currently using the gorilla/mux package to make an API with a few handlers that depend on multiple external resources (a Redis DB for example). The fact is, HTTP Handlers only use http.ResponseWriter and *http.Request as parameter, so I used some random dependency injection pattern I've found online.

The structure of my project goes as follows :
- subpackage "contact" containing the HTTP Handler as well as the Inject method shown below
- subpackage "config" containing a goroutine using fsnotify+mutex to refresh internal configuration if modified + an opened connection to a *redis.Client
- subpackage "mapping" which handles other files on the system in the same way as Config ; updates an internal map[] with new data
- subpackage "contents", same idea, mysql data source to refresh a map every once in a while

Here's a few code showing the current flow :

- Router initialization :

myRouter.Handle("/endpoint1/", contact.Inject(config.GetRedisDb(), contents.GetUpdater(), mapping.GetUpdater(), contact.Handle)).Methods("POST", "OPTIONS")

- contact.Inject code :

func Inject(
    red *redis.Client,
    u *content.Updater,
    m *mapping.Updater,
    f func(red *redis.Client, u *content.Updater, mapper *mapping.Updater, w http.ResponseWriter, r *http.Request),
) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        f(red, u, m, w, r)
    })
}

- contact.Handle signature :

func Handle(rdsClient *redis.Client, u *content.Updater, m *mapping.Updater, w http.ResponseWriter, r *http.Request) {}

My issue is that the code feels quite "heavy". It works quite well and I'm pretty sure performance impact is quite minimal by injecting resources this way, but readability is quite bad, and now that I actually need to add a completely new dependency, I'm not sure I want to keep doing this.

Is there any better way you guys know of? Is my method being annoying to read through still considered as good?

Thanks!

Nathanael




Andrew Pillar

unread,
Sep 23, 2019, 5:20:52 AM9/23/19
to golan...@googlegroups.com
Why don't you use context.WithValue(r.Context(), <key>, <value>) for passing
the injected values you want to your HTTP handlers. Then defer the logic for
retrieving whatever you want for that handler to a middleware function.

Nathanael Curin

unread,
Sep 23, 2019, 5:37:47 AM9/23/19
to golang-nuts
I'm avoiding context.WithValue since I feel the objects I'm dealing with are too "powerful" (i.e. not simple Values). For a read, a 2016 article on this : https://peter.bourgon.org/blog/2016/07/11/context.html
I remember reading through another article saying the usage of WithValue is kind of risky when time goes on, since it's just a magical interface{} store. Performance-wise I'm not sure though.

Though, you're right, that'd be another way to do it. Waiting for people to chime in with their ideas too (hopefully!) :)

Abhinav Gupta

unread,
Sep 23, 2019, 12:35:06 PM9/23/19
to Nathanael Curin, golang-nuts

You can place the dependencies into a struct and use a bound method on that as your handler. So you build a struct that will hold application-scoped objects like Redis client, content.Updater, etc.

package contact

type Handler struct {
    Redis   *redis.Client
    Updater *content.Updater
    Mapper  *mapping.Updater
}

func (h *Handler) Endpoint1(w http.ResponseWriter, r *http.Request) {
    // ...
}

Then build the Handler when you’re setting up the router (presumably in main() or nearby) and register it against the router as usual.

handler := contact.Handler{
    Redis:   config.GetRedisDb(),
    Updater: contents.GetUpdater(),
    Mapper:  mapping.GetUpdater(),
}

myRouter.Handle("/endpoint1/", http.HandlerFunc(h.Endpoint1)).Methods("POST", "OPTIONS")
// or
myRouter.HandleFunc("/endpoint1/", h.Endpoint1).Methods("POST", "OPTIONS")


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/c23761e8-1287-4758-8303-b39db8cf22f0%40googlegroups.com.

Nathanael Curin

unread,
Sep 25, 2019, 9:48:59 AM9/25/19
to golang-nuts
Oh I love this! Not too verbose but still understandable, clean, and easy to add stuff. I didn't even think of using a method as a handler.

Absolutely gonna go for this. Thanks!
To unsubscribe from this group and stop receiving emails from it, send an email to golan...@googlegroups.com.

George Hartzell

unread,
Sep 25, 2019, 5:43:24 PM9/25/19
to Nathanael Curin, golang-nuts
Le lundi 23 septembre 2019 18:35:06 UTC+2, Abhinav Gupta a écrit :
> [...]
> myRouter.Handle("/endpoint1/", http.HandlerFunc(h.Endpoint1)).Methods("POST", "OPTIONS")
> // or
> myRouter.HandleFunc("/endpoint1/", h.Endpoint1).Methods("POST", "OPTIONS")
>

Should that instead be (replacing 'h.Endpoint1' with 'handler.Endpoint1')?

myRouter.Handle("/endpoint1/", http.HandlerFunc(handler.Endpoint1)).Methods("POST", "OPTIONS")
// or
myRouter.HandleFunc("/endpoint1/", handler.Endpoint1).Methods("POST", "OPTIONS")

g.
Reply all
Reply to author
Forward
0 new messages