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.New
orfmt.Errorf
to create meaningful error messages. - Consider using
errors.Is
anderrors.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 likesqlx
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 liketestify
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
orgolangci-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! 🚀