best practice of go backend service 1

Table of Contents

best practice

1. Project Structure

Organize your project in a logical and consistent way. A common structure for a Go backend service is:

/my-service
  ├── /cmd
  │   └── /my-service
  │       └── main.go
  ├── /internal
  │   ├── /handlers
  │   ├── /models
  │   ├── /services
  │   └── /repositories
  ├── /pkg
  │   └── /utils
  ├── /configs
  ├── /migrations
  ├── /api
  ├── /scripts
  └── go.mod
  • cmd/my-service/main.go: Entry point of your application.
  • internal/: Contains the core application logic. This directory is private to your module.
  • pkg/: Reusable utility functions or libraries.
  • configs/: Configuration files.
  • migrations/: Database migration scripts.
  • api/: API definitions (e.g., OpenAPI/Swagger specs).
  • scripts/: Helper scripts for deployment, testing, etc.

2. Use Dependency Injection

Avoid global variables and tightly coupled code. Pass dependencies (e.g., database connections, services) explicitly to functions or structs.

type UserService struct {
    repo UserRepository
}

func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

func (s *UserService) GetUser(id int) (*User, error) {
    return s.repo.FindByID(id)
}

3. Error Handling

  • Always handle errors explicitly.
  • Use errors.New or fmt.Errorf to create meaningful error messages.
  • Consider using errors.Is and errors.As for error comparisons and type assertions.
if err != nil {
    return fmt.Errorf("failed to fetch user: %w", err)
}

4. Logging

Use a structured logging library like logrus or zap for better log management.

import (
    "github.com/sirupsen/logrus"
)

func main() {
    log := logrus.New()
    log.Info("Starting the application...")
}

5. Configuration Management

Use environment variables or configuration files (e.g., JSON, YAML) to manage settings. Libraries like viper can help.

import (
    "github.com/spf13/viper"
)

func init() {
    viper.SetConfigFile("config.yaml")
    if err := viper.ReadInConfig(); err != nil {
        log.Fatalf("Error reading config file: %v", err)
    }
}

6. Database Access

  • Use an ORM like gorm or a query builder like sqlx for database interactions.
  • Always use prepared statements to avoid SQL injection.
import (
    "github.com/jmoiron/sqlx"
    _ "github.com/lib/pq"
)

func main() {
    db, err := sqlx.Connect("postgres", "user=postgres dbname=mydb sslmode=disable")
    if err != nil {
        log.Fatalln(err)
    }
    defer db.Close()
}

7. API Design

  • Use RESTful principles for designing APIs.
  • Group related routes and use middleware for cross-cutting concerns (e.g., logging, authentication).
import (
    "github.com/gorilla/mux"
    "net/http"
)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/users", GetUsers).Methods("GET")
    r.HandleFunc("/users/{id}", GetUser).Methods("GET")
    http.ListenAndServe(":8080", r)
}

8. Testing

  • Write unit tests for your functions and integration tests for your APIs.
  • Use the testing package and tools like testify for assertions.
import (
    "testing"
    "github.com/stretchr/testify/assert"
)

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    assert.Equal(t, 5, result)
}

9. Concurrency

Leverage Go's concurrency primitives (goroutines and channels) for tasks that can run in parallel.

func process(data chan int) {
    for d := range data {
        fmt.Println("Processing:", d)
    }
}

func main() {
    data := make(chan int)
    go process(data)

    for i := 0; i < 10; i++ {
        data <- i
    }
    close(data)
}

10. Graceful Shutdown

Handle shutdown signals to clean up resources (e.g., database connections) before exiting.

import (
    "context"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    server := &http.Server{Addr: ":8080"}

    go func() {
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("Server shutdown error: %v", err)
    }
}

11. Documentation

  • Use Go's built-in documentation tool (go doc) to document your code.
  • Write clear and concise comments for exported functions, types, and packages.
// Add adds two integers and returns the result.
func Add(a, b int) int {
    return a + b
}

12. Versioning

Use semantic versioning for your APIs and libraries. For example:

/v1/users
/v2/users

13. Use Linters and Formatters

  • Use gofmt to format your code.
  • Use golint or golangci-lint to enforce coding standards.

14. Monitor and Profile

  • Use tools like pprof for profiling your application.
  • Monitor performance and resource usage in production.

By following these best practices, you can build robust, maintainable, and scalable backend services in Go. Happy coding! 🚀

Comments |0|

Legend *) Required fields are marked
**) You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Category: golang