DEV Community

Max Cian
Max Cian

Posted on

Don't use routes grouping functionality: a chance to rethinking your mindset of code reuse

Issue

I've seen a lots of Go codes in my company's codebase, just like the example on Gin's Group routes, use this functionality to grouping the RESTful endpoints by just pulling some common prefix out for the sake of "reusability":

router := gin.Default()
api := router.Group("/api")
api.POST("/login", loginEndpoint)
api.POST("/submit", submitEndpoint)
api.POST("/read", readEndpoint)
Enter fullscreen mode Exit fullscreen mode

Even more, when we defined more RESTful manipulations on a resource, we will more likely to refactor our code like this:

api := router.Group("/api")
// ...
notification := api.Group("/notification")
notification.GET("/register", registerEndpoint)
notification.POST("/token/exchange", exchangeTokenEndpoint)
notification.POST("/token/revoke", revokeEndpoint)
// ...
Enter fullscreen mode Exit fullscreen mode

If you ask yourself (or the junior engineer) why you code like this, probably the answer is: I want to increase the code reusability for trying to make the code is better.

So, what kind of problem behind this code?

Reduce debugging time is more valuable for your life

It is very common to receive a bug description like following when your code has been deployed a few weeks:

"Client side receive an unexpected response like following..., when calling /api/notification/token/revoke endpoint with some request parameters..."

OK, we have a bug need to fix, and you know it is very possible there is a bug in the handling routine on that endpoint.

Thereafter, you open your IDE (e.g. VSCode) and search by the given endpoint /api/notification/token/revoke trying to find what handle function actually handling this endpoint.

But, nothing you got from searching results. Because you split your URL path of endpoint into several parts by using the routes grouping functionality.

So you try to search api or revoke word-by-word appeared on your codebase to approach the handle function, these journey is not very happy and time-consuming because you can expect that there are many noises on searching results.

Rethink the benefits of code reusability

The benefits of code reuse is not just only thinking about you seem to reduce the development time when you create more and more routes, but should put more considerations on how you and other codebase maintainers would maintain and troubleshoot these codes.

I assume most programmers are more like coding instead of debugging, especially the bugged codes may be written in an unfamiliar coding style to you. (means the codes may be written by someone else or YOU in a few weeks ago.)

No one are happy to debug an unfamiliar codes, because you need to pay more cognitive loading to understand the codes. This activity is very unhappy and painful.

So, how to ease that pain?

Proposed use cases of routes grouping functionality

From my experience, there is no benefits to group the routes by some prefixes or common path, but increasing the debugging cost in the future.

So, if you have no other actual requirements to use grouping functionality, just define routes in the explicitly way:

router := gin.Default()
// ...
router.GET("/api/notification/register", registerEndpoint)
router.POST("/api/notification/token/exchange", exchangeTokenEndpoint)
router.POST("/api/notification/token/revoke", revokeEndpoint)
// ...
Enter fullscreen mode Exit fullscreen mode

But what scenario you should actually consider to use routes grouping functionality? Some endpoints require authentication and some not.

The use case may looks like:

router := gin.Default()
noAuth := router.Group("/")
noAuth.POST("/api/auth/login", loginEndpoint)

auth := router.Group("/", authMiddleware)
auth.GET("/api/notification/register", registerEndpoint)
auth.POST("/api/notification/token/exchange", exchangeTokenEndpoint)
auth.POST("/api/notification/token/revoke", revokeEndpoint)
// ...
Enter fullscreen mode Exit fullscreen mode

Conclusion

  • Re-consider when you want to split your route definition into several parts, does it really gain any benefits?
  • Put more considerations on how you would maintain your codes, try to find a way which suitable for you to ease the pain when debugging and troubleshooting

Top comments (4)

Collapse
 
clavinjune profile image
Clavin June

it doesn't increase debugging cost if you have a standard convention on creating groups, for example:

api := r.Group("/api")

notification.NewHandler(api.Group("/notification")
// ... adding more handler with the same conventions
Enter fullscreen mode Exit fullscreen mode

now you'll know where to debug when the endpoint has an /api/notification prefix

Collapse
 
hjcian profile image
Max Cian

I agree with that your suggestion is a good refactor strategy.

And, to be honest, I also agreee if our team have more discussions on how to organize our routes definition as standard conventions, the debugging/troubleshooting cost may not increase signifinatly when we have a lots of complex routes.

But the thing I want to emphasize is that any small code reuse strategy should be careful to adopt before you have confirmed that benefits of reducing code complexity are truly exists, both development time and maintainence activity.

Collapse
 
codewander profile image
codewander • Edited

If you were using a library that was analogous to flask restful (which I have superficial knowledge of), would you still have the "go restful" add flattened routes or using grouping in the library implementation?

Collapse
 
codewander profile image
codewander

Reduce debugging time

I am not sold on this one. The routes module is usually easy to read through and in an obvious location.