Showing posts with label Gin. Show all posts
Showing posts with label Gin. Show all posts

Tuesday, 14 January 2025

Golang, Gin: chainable method in Struct

In PHP, a chainable method in a class is one that returns the object itself (typically $this), allowing multiple methods to be called on the same object in a single line, like:

php
class Example { public function method1() { // do something return $this; } public function method2() { // do something else return $this; } } $example = (new Example())->method1()->method2();

In Go (Golang), while Go doesn't have the same concept of classes as in PHP, you can achieve similar behavior using structs and methods that return a pointer to the struct itself. This allows you to chain method calls in a similar way. If you're using the Gin framework, it typically doesn't have a built-in chainable pattern, but you can create chainable methods for your custom structs.

Here’s how you might implement a chainable pattern with Go:

go
package main import "fmt" type Example struct { value int } func (e *Example) Method1() *Example { e.value += 1 return e } func (e *Example) Method2() *Example { e.value *= 2 return e } func main() { e := &Example{} e.Method1().Method2() fmt.Println(e.value) // Output will be 2 (1 + 1, then 2 * 1) }

In this example, each method modifies the Example struct and returns a pointer to the same struct, allowing you to chain calls.

While Gin doesn't inherently encourage chainable methods, you could extend it for custom use cases by applying this pattern in your own code for convenience.

Golang, Gin: Struct

Use a Struct (like Class) to encapsulate dependencies, methods, and functionality.

Here's an example of a single struct with attributes and methods in Go to demonstrate how a struct can encapsulate both data (attributes) and behavior (methods).

Example: Single Struct with Attributes and Methods

package main import ( "fmt" ) ================================= // User struct with attributes and methods type User struct { ID int Name string Age int } ================================= // Method: Displays user details func (u *User) DisplayDetails() { fmt.Printf("User ID: %d\n", u.ID) fmt.Printf("Name: %s\n", u.Name) fmt.Printf("Age: %d\n", u.Age) } ================================= // Method: Updates the user's name func (u *User) UpdateName(newName string) { u.Name = newName fmt.Println("Name updated successfully!") } ================================= // Method: Checks if the user is an adult func (u *User) IsAdult() bool { return u.Age >= 18 } ================================= func main() { // Create a new User instance user := User{ ID: 1, Name: "John Doe", Age: 25, } // Call methods on the User struct fmt.Println("Initial User Details:") user.DisplayDetails() // Update the user's name user.UpdateName("Jane Doe") fmt.Println("\nUpdated User Details:") user.DisplayDetails() // Check if the user is an adult if user.IsAdult() { fmt.Println("\nThe user is an adult.") } else { fmt.Println("\nThe user is not an adult.") } }

Explanation of the Code

  1. Struct Definition:

    • The User struct represents a user with three attributes: ID, Name, and Age.
  2. Methods:

    • DisplayDetails: Prints the user's details to the console.
    • UpdateName: Updates the user's name to a new value.
    • IsAdult: Checks if the user's age is 18 or older and returns a boolean.
  3. Creating and Using the Struct:

    • An instance of User is created with initial values.
    • Methods are called to interact with the instance, modifying its attributes and performing operations.

Output of the Program

Initial User Details: User ID: 1 Name: John Doe Age: 25 Name updated successfully! Updated User Details: User ID: 1 Name: Jane Doe Age: 25 The user is an adult.

Summary

This is how you can use a single struct in Go to encapsulate both data (attributes) and behavior (methods), similar to a class in PHP.

Golang Gin: Implementing Interfaces

 interfaces define a set of methods. Any type that implements these methods satisfies the interface.

Example of an Interface Implementation:

package main ========================== import "fmt" // Interface definition type Printable interface { PrintInfo() string } ========================== // Struct that implements the interface type User struct { Name string Email string } ========================== // Implement the interface func (u User) PrintInfo() string { return fmt.Sprintf("Name: %s, Email: %s", u.Name, u.Email) } ========================== func main() { user := User{Name: "Le Giang", Email: "le.giang@example.com"} var p Printable = user // User satisfies the Printable interface fmt.Println(p.PrintInfo()) }

Output:

Name: Le Giang, Email: le.giang@example.com

Golang Gin: Composition Instead of Inheritance

Composition Instead of Inheritance

In Go, you can "extend" functionality by embedding structs. When a struct embeds another struct, it inherits its methods and fields.

Example of Composition (Struct Embedding):

package main =========================== import "fmt" // Base struct type Base struct { ID int } // Method of Base struct func (b *Base) PrintID() { fmt.Printf("ID: %d\n", b.ID) } =========================== // Extended struct using composition type Extended struct { Base // Embedding Base struct Name string }
=========================== func main() { // Initialize Extended ext := Extended{ Base: Base{ID: 42}, Name: "Le Giang", } // Access methods from Base ext.PrintID() // Access field from Extended fmt.Println("Name:", ext.Name) }

Output:

ID: 42 Name: Le Giang

Thank

Folder structure often used with Gin

Below is an example of a folder structure often used with Gin in large-scale projects. Gin itself does not enforce a strict layout, because it follows a minimalistic philosophy. However, the community usually follows certain best practices to keep the code organized.

my-gin-app/
├── cmd/
│   └── server/
│       └── main.go
├── config/
│   └── config.go
├── controllers/    (or handlers/)
│   └── user_controller.go
├── middlewares/
│   └── auth.go
├── models/
│   └── user.go
├── routes/
│   └── router.go
├── services/       (business logic)
│   └── user_service.go
├── repository/     (DB access, optional)
│   └── user_repository.go
├── go.mod
└── go.sum

Directory Overview (Suggested)

  1. cmd/server/main.go

    • The application’s entry point.
    • Initializes the Gin engine, loads configs, sets up routes, etc.
    • Placing main.go inside cmd/server/ is a common Go convention to keep the root clean.
  2. config/

    • Handles configuration (e.g., reading .env, using Viper, etc.).
    • For instance, config.go might have func InitConfig() to load environment variables or database connection info.
  3. controllers/ (or handlers/)

    • Houses functions that handle requests (CRUD, login, etc.).
    • Example: user_controller.go has func GetUsers(c *gin.Context) and func CreateUser(c *gin.Context).
  4. middlewares/

    • Contains middleware (auth, logging, recovery, etc.).
    • Example: auth.go with func AuthMiddleware() gin.HandlerFunc.
  5. models/

    • Contains structs mapping to database tables (e.g., User with ID, Name, Email...).
    • If using GORM, add the appropriate tags (gorm:"...").
  6. routes/

    • Contains a SetupRouter() function to group and register routes, apply middleware, etc.

    • Example:

      package routes
      
      import (
        "github.com/gin-gonic/gin"
        "my-gin-app/controllers"
        "my-gin-app/middlewares"
      )
      
      func SetupRouter() *gin.Engine {
        r := gin.Default()
      
        r.Use(middlewares.LoggerMiddleware())
      
        user := r.Group("/users")
        {
          user.GET("/", controllers.GetUsers)
          user.POST("/", controllers.CreateUser)
        }
        return r
      }
      
  7. services/

    • Contains business logic (e.g., user_service.go for email sending, domain rules, etc.).
    • Your controllers can remain thin, delegating heavier tasks to these service functions.
  8. repository/ (optional)

    • Separates all database interactions (queries) from services, promoting cleaner architecture.
    • In simpler projects, you can skip this folder and keep queries in services.
  9. go.mod / go.sum

    • Standard Go files for dependency management.
  10. (Additional directories)

    • migrations/ (if using Goose or GORM migrations).
    • test/ (unit or integration tests).
    • docs/ (API documentation, Swagger, etc.).
    • scripts/ (CI/CD scripts).

Mini Example

cmd/server/main.go

package main

import (
    "log"

    "my-gin-app/config"
    "my-gin-app/routes"
)

func main() {
    // 1. Initialize config, DB, etc.
    config.InitConfig()

    // 2. Set up Gin router
    r := routes.SetupRouter()

    // 3. Run
    if err := r.Run(":8080"); err != nil {
        log.Fatal(err)
    }
}

config/config.go

package config

import (
    "fmt"
    "os"
    // import "github.com/joho/godotenv" if you need to load .env
    // import GORM libraries if you plan to connect to a DB
)

func InitConfig() {
    // For example: load environment variables
    // godotenv.Load()

    fmt.Println("Config loaded, database connected... (placeholder)")
}

models/user.go

package models

import "time"

type User struct {
    ID        uint      `gorm:"primaryKey"`
    Name      string    `gorm:"size:255"`
    Email     string    `gorm:"size:255;unique"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

controllers/user_controller.go

package controllers

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "my-gin-app/services"
)

func GetUsers(c *gin.Context) {
    users, err := services.GetAllUsers()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    c.JSON(http.StatusOK, users)
}

func CreateUser(c *gin.Context) {
    var input struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user, err := services.CreateUser(input.Name, input.Email)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    c.JSON(http.StatusOK, user)
}

services/user_service.go

package services

import (
    "fmt"
    "my-gin-app/models"
)

// Mocked data for illustration
func GetAllUsers() ([]models.User, error) {
    return []models.User{
        {ID: 1, Name: "Alice", Email: "[email protected]"},
        {ID: 2, Name: "Bob", Email: "[email protected]"},
    }, nil
}

func CreateUser(name, email string) (models.User, error) {
    if email == "" {
        return models.User{}, fmt.Errorf("email is required")
    }
    // Suppose we insert into DB and return the created record
    user := models.User{
        ID:    3,
        Name:  name,
        Email: email,
    }
    return user, nil
}

routes/router.go

package routes

import (
    "github.com/gin-gonic/gin"
    "my-gin-app/controllers"
)

func SetupRouter() *gin.Engine {
    r := gin.Default()

    userGroup := r.Group("/users")
    {
        userGroup.GET("/", controllers.GetUsers)
        userGroup.POST("/", controllers.CreateUser)
    }

    return r
}

Conclusion

  • Gin does not provide a default or strict folder structure. Instead, it trusts developers to organize the project.
  • For mid- to large-sized projects, many people separate controllers, services, models, routes, middlewares, etc., to keep the codebase maintainable.
  • The example above is a commonly used pattern in the Go community:
    1. cmd/server/: main entry point
    2. config/: app configurations (env, DB)
    3. controllers/ (or handlers/): handling HTTP requests
    4. services/: business/domain logic
    5. models/: database schemas (e.g., with GORM)
    6. routes/: routing setup
    7. middlewares/: shared logic for requests (JWT, logging, etc.)
    8. repository/: optional layer for DB queries
    9. go.mod/go.sum: module dependencies

Feel free to modify this structure according to your team’s needs and project size.

Publish npm package

  Để publish   pav-kit  lên NPM, bạn hãy làm theo các bước dưới đây. Tôi đã tạo thêm file  index.js  để đảm bảo gói tin hợp lệ. Bước 1: Tạo ...