October 18, 2020

Add a Server Struct for Better Handlers Management

On my previous post, I was explaining how to access a database from inside a HTTP handler in go using closures. While this method works, it comes with the disadvantage that if you want to access more than the database, you’ll have to add the new element to the list of arguments. In every single handler. On top of that, closures are not that simple to understand, and may lead to confusion.

As pointed out by some kind readers from /r/golang, a better way to go about this is to create the handlers on a Server struct which contains the elements you need on the inside. This is the point that was next on the this blog post post from Mat Ryer which introduced me to the clojure pattern. I did not understand enough to see the difference between the two at the time, so thank you /u/nikandfor and /u/dumindunuwan for your feedback !

Let’s look at the code from the previous post and improve it using this pattern. Before, we had:

// HandleGetUser will search a user by its username, and return its data if it exists
// Store is an interface allowing to get elements from the database
func HandleGetUser(store Store) func(c *gin.Context) {
    // We return a function that matches the interface needed by the router
	return func(c *gin.Context) {
		username := c.Param("username")
	    // But can still use the database interface within the returned function
		u, err := store.GetUserByUsername(username)
		if err != nil {
			c.JSON(404, gin.H{"error": "Could not find user with this username"})
			return
		}
		c.JSON(200, u)
	}
}

Let’s create a Server struct with access to the store and refactor the handler to hang off of this server:

type Server struct {
	Store Store
}

// HandleGetUser will search a user by its username, and return its data if it exists
func (s *Server) HandleGetUser(c *gin.Context) {
	username := c.Param("username")
	u, err := s.Store.GetUserByUsername(username)
	if err != nil {
		c.JSON(404, gin.H{"error": "Could not find user with this username"})
		return
	}
	c.JSON(200, u)
}

Even at first sight, this looks much cleaner, and will be easier to maintain. If I want, for example, to add a logger to this, I can now add it to the server and simply access it in the handler.

Copyright Marin Gilles 2019-2022