Many Routes to Gin

How various ways of declaring routes on Gin affects performance

Fri, Oct 9, 2020 3-minute read

With great choices comes greater confusion. The flexible ways to declare routes in Gin Web Framework means raise questions about whether any particular way is more performant. This post seeks to answer that question.

Background

One fine day, I was working with Go and the Gin Web Framework, trying to structure the code base in a way to enhance maintainability. In particular, I wanted to understand how the following two ways of setting up routes in the Gin Web Framework would affect routing performance (if at all):

  // Approach #1: Using route group
  routeGroup := r.Group("/basePath", AuthMiddleware)
  {
      routeGroup.POST("/endpoint1", RouterHandler1)
      routeGroup.POST("/endpoint2", RouterHandler2)
      routeGroup.POST("/endpoint3", RouterHandler3)
  }

  // Approach #2: Plain declarations
  r.POST("/basePath/endpoint1", AuthMiddleware, RouterHandler1)
  r.POST("/basePath/endpoint2", AuthMiddleware, RouterHandler2)
  r.POST("/basePath/endpoint3", AuthMiddleware, RouterHandler3)

The short answer is that either approach works fine, so you are free to choose whichever style suits your code base.

But if you are not convinced, do read on for how I convinced myself of this conclusion by (a) exploring the Gin's documentation and code base, and (b) doing some basic debugging to examine the route object generated using each approach.

Looking up Documentation and Code base

My first instinct was to look up the godoc of Gin Web Framework, to check whether there is anything in the Group() method regarding performance: here.

  • Unfortunately, there was nothing in the documentation. However, I did notice that each of the GET(), POST() etc. methods are in fact just shortcuts for the router.Handle(...) method.

So the second thing I did was to click through on the godoc of the Handle(...) method to jump to the github repository, and traced through this, this and this until I arrived at the addRoute(...) method in tree.go.

  • Scrolling through the body of addRoute(...), I see functions and variables like longestCommonPrefix() and parentFullPathIndex, and I knew there will be some resolution of the routes and handlers.

  • (On hindsight, how silly of me to have not foresee this: how else would a routing component makes sure no routes are duplicated, and to prioritize handlers when there are multiple matches.)

In any case, the above gave me confidence that either of the approaches listed at the beginning of this post would be similar, if not identical.

Examining actual router struct by debugging

Nonetheless, as I'm still very new to Go, I decided to set up a simple toy project to find out whether there are indeed any difference between the two approaches. In particular, I wanted to dig into the guts of the router struct to see whether there are any differences from the two approaches.

The code I used is as below. (Note that I am using go-spew library to print the entire structure of the final struct out, before running a diff with my favorite editor.)

  package main

  import (
      "github.com/davecgh/go-spew/spew"
      "github.com/gin-gonic/gin"
  )

  func AuthMiddleware(c *gin.Context) {
      // Does nothing
  }

  func RouterHandler(c *gin.Context) {
      // Does nothing
  }

  func main() {
      r := gin.Default()

      // Approach #1: Using route group
      // routeGroup := r.Group("/basePath", AuthMiddleware)
      // {
      // 	routeGroup.POST("/endpoint1", RouterHandler)
      // 	routeGroup.POST("/endpoint2", RouterHandler)
      // 	routeGroup.POST("/endpoint3", RouterHandler)
      // }

      // Approach #2: Plain declarations
      // r.POST("/basePath/endpoint1", AuthMiddleware, RouterHandler)
      // r.POST("/basePath/endpoint2", AuthMiddleware, RouterHandler)
      // r.POST("/basePath/endpoint3", AuthMiddleware, RouterHandler)

      spew.Dump(r) // Set breakpoint here to view r in debugger.
  }

And here's the diff (no difference except the pointer address to the handlers):

/img/post-gin-router-performance-diff.png

Conclusion

And that's it, my brief exploration into a small part of the routing mechanics of Gin Web Framework.