Sharing Resources In Your Go Code

Sharing Resources In Your Go Code

To develop a web application using Go we most probably use one of the microframeworks like Gin, Echo, Fiber, and more. These microframeworks give us the essential components like Server, routing, middleware, Templates, and more for building our application. But they don't say anything about how to organize the code. As I am from the PHP realm I want to take the Laravel framework as an example for a full-stack framework. So in Laravel, they lay out the best plan to organize your files and data. Here's a screenshot of the File structure of a Laravel application.

laravelfiles.png In a full-stack framework, all the components are already stitched together, so that the developer can concentrate on implementing the business logic. You can access all the external components for the application like database, Redis, and more using the framework provided classes and methods in the best way. But when comes to the microframeworks you have to take care of the file organization and data management yourself. One of the most common of these things is managing database connections and making them available within our files as needed.

For example, consider we are developing a simple User Management CRUD application. Here is an example where we wrote all the code in the main.go file itself.

package main

import (
    "github.com/gofiber/fiber/v2"
)

type User struct {
    Name string
}

func main() {
    app := fiber.New()
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    app.Get("/users", func(c *fiber.Ctx) error {
        users := []User{}
        db.Limit(10).Find(&users)
        return c.SendString("Sending list of users")
    })
    app.Post("/users", func(c *fiber.Ctx) error {
        user := User{}
        db.Create(&user)
        return c.SendString("Saving a user")
    })
    app.Get("/users/:id", func(c *fiber.Ctx) error {
        user := User{}
        db.First(&user, c.Params["id"])
        return c.SendString("Fetching a user")
    })
    app.Put("/users/:id", func(c *fiber.Ctx) error {
        user := User{}
        db.First(&user, c.Params["id"])
        user.Name = c.FormValue("name")
        db.Save(&user)
        return c.SendString("Updating a user")
    })
    app.Delete("/users/:id", func(c *fiber.Ctx) error {
        user := User{}
        db.First(&user, c.Params["id"])    
        db.Delete(&user)
        return c.SendString("Deleting a user")
    })

    app.Listen(":3000")
}

Note: This is not a working code. As you can see the code is straightforward with database access available to all your code but also observe how quickly can your code will become complex and crowded impossible to manage in a single function and file. We have to break it down into multiple files. One of our main objectives is to keep the main.go file clean and tidy. We can move the database initialization and routes into their own files keeping the main.go file tidy. We can create a package called routes and have those above routes defined in a file called api.go

package routes

import (
    "github.com/gofiber/fiber/v2"
    "github.com/harishdurga/users/controllers"
)

func Init(app *fiber.App,db *gorm.DB) {
    api := app.Group("/api")
    v1 := api.Group("/v1")
    users := v1.Group("/users")
    users.Get("/", controllers.ListUsers)
}

and the database initialization part to database/db.go.

app := fiber.New()
db := database.Init()
routes.Init(app,db)

Also, observe the code to handle users has been moved into the controllers/users.go. Now the question is how can we access the database connection inside the controller functions. The bad way to do it can be something like this:

users.Get("/",controllers.ListUsers(context,db))
users.Post("/",controllers.SaveUser(context,db))
users.Get(":id",controllers.GetUser(context,db))
users.Put(":id",controllers.UpdateUser(context,db))

At first, it won't look that bad. But what if you want the cache and logger to be available inside your controller functions.

controllers.GetUser(context,db,cache,logger)
...

As we can see that the functions are getting bloated with arguments and certainly this is not the way forward. So to do this is a clean way we can make use of the receiver functions concept. Let's create a controllers/base.go and add

type BaseHandler struct{
db *gorm.DB
cache *redis
logger *zap.SugarLogger
...
}
func NewBaseHandler(db *gorm.DB,cache *redis,logger *zap.SugerLogger)*BaseHandler{
  return &BaseHandler{db:db,cache:cache,logger:logger}
}

In routes/api.go

func Init(app *fiber.App,db *gorm.DB,logger *zap.SugarLogger,...){
 baseHandler := controllers.NewBaseHandler(db,logger,cache)
 app.Get("/user/:id",baseHandler.GetUser)
}

Not only these three but we can define all the resources that we need in our controller functions. In the bad case, the function in the controller will be like

func GetUser(context,db,cache,logger){
//code
}

In the other case, the controller function will be a receiver function for the BaseHandler struct.

//controllers/users.go
func (bh *BaseHandler) GetUser(context){
 bh.db //Access Database
 bh.cache //Access to cache
 bh.logger.Info("Hello world") //Access to logger
}
func (bh *BaseHandler) SaveUser(context){
 //Code
}
func (bh *BaseHandler) ListUsers(context){
 //Code
}

Also if you have another controller for example controllers/books.go

package controllers

func (bh *BaseHandler) SaveBook(context){
 //Code
}
func (bh *BaseHandler) ListBooks(context){
 //Code
}
...

This way you can share as much data to your code split into separate files and packages in a clean way. Please refer to this article https://www.alexedwards.net/blog/organising-database-access to know more about this as the author explains other ways of doing this. Though the one we discussed here is the better way of it.

Thank you for your time. Please feel free to let me know your queries regarding the article. If you like the article kindly like and follow for more articles like this one.