best practice of go backend service 1
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.Neworfmt.Errorfto create meaningful error messages. - Consider using
errors.Isanderrors.Asfor 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
gormor a query builder likesqlxfor 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
testingpackage and tools liketestifyfor 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
gofmtto format your code. - Use
golintorgolangci-lintto enforce coding standards.
14. Monitor and Profile
- Use tools like
pproffor 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! 🚀