高质量 Golang 后端的现代软件工程原则
写出能运行的代码很容易,写出高质量的代码需要遵循工程原则。
一、项目结构
1.1 标准目录结构
myapp/
├── cmd/ # 应用程序入口
│ ├── api/ # HTTP API 服务
│ │ └── main.go
│ └── worker/ # 后台任务
│ └── main.go
├── internal/ # 私有代码
│ ├── domain/ # 领域模型
│ │ ├── user.go
│ │ └── order.go
│ ├── service/ # 业务逻辑
│ │ ├── user_service.go
│ │ └── order_service.go
│ ├── repository/ # 数据访问
│ │ ├── user_repo.go
│ │ └── order_repo.go
│ ├── handler/ # HTTP 处理
│ │ ├── user_handler.go
│ │ └── order_handler.go
│ └── infrastructure/ # 基础设施
│ ├── database/
│ ├── cache/
│ └── queue/
├── pkg/ # 公共库
│ ├── logger/
│ ├── errors/
│ └── utils/
├── configs/ # 配置文件
├── deployments/ # 部署配置
├── scripts/ # 脚本
├── docs/ # 文档
├── tests/ # 测试
├── go.mod
├── go.sum
├── Makefile
├── Dockerfile
└── README.md1.2 分层架构
┌─────────────┐
│ Handler │ HTTP 处理、参数校验
├─────────────┤
│ Service │ 业务逻辑、事务管理
├─────────────┤
│ Repository │ 数据访问、持久化
├─────────────┤
│ Domain │ 领域模型、业务规则
└─────────────┘二、依赖管理
2.1 接口隔离
go
// 定义接口
package repository
type UserRepository interface {
GetByID(ctx context.Context, id int64) (*domain.User, error)
GetByEmail(ctx context.Context, email string) (*domain.User, error)
Create(ctx context.Context, user *domain.User) error
Update(ctx context.Context, user *domain.User) error
Delete(ctx context.Context, id int64) error
}
// 实现
type userRepository struct {
db *sql.DB
}
func NewUserRepository(db *sql.DB) UserRepository {
return &userRepository{db: db}
}2.2 依赖注入
go
// 使用 wire 进行依赖注入
// wire.go
//go:build wireinject
// +build wireinject
package main
import (
"github.com/google/wire"
)
func InitializeApp() (*App, error) {
wire.Build(
database.NewDB,
repository.NewUserRepository,
service.NewUserService,
handler.NewUserHandler,
NewApp,
)
return &App{}, nil
}三、错误处理
3.1 自定义错误
go
package errors
import (
"errors"
"fmt"
)
// 错误类型
type ErrorType int
const (
ErrorTypeUnknown ErrorType = iota
ErrorTypeNotFound
ErrorTypeInvalidInput
ErrorTypeUnauthorized
ErrorTypeForbidden
ErrorTypeInternal
)
// 应用错误
type AppError struct {
Type ErrorType
Code string
Message string
Err error
}
func (e *AppError) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %v", e.Message, e.Err)
}
return e.Message
}
func (e *AppError) Unwrap() error {
return e.Err
}
// 错误构造函数
func NotFound(code, message string) *AppError {
return &AppError{
Type: ErrorTypeNotFound,
Code: code,
Message: message,
}
}
func Internal(code string, err error) *AppError {
return &AppError{
Type: ErrorTypeInternal,
Code: code,
Message: "internal server error",
Err: err,
}
}3.2 错误处理最佳实践
go
// 服务层
func (s *userService) GetUser(ctx context.Context, id int64) (*domain.User, error) {
user, err := s.repo.GetByID(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, errors.NotFound("USER_NOT_FOUND", "user not found")
}
return nil, errors.Internal("DB_ERROR", err)
}
return user, nil
}
// 处理层
func (h *userHandler) GetUser(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(400, gin.H{"error": "invalid user id"})
return
}
user, err := h.service.GetUser(c.Request.Context(), id)
if err != nil {
var appErr *errors.AppError
if errors.As(err, &appErr) {
switch appErr.Type {
case errors.ErrorTypeNotFound:
c.JSON(404, gin.H{"error": appErr.Message})
default:
c.JSON(500, gin.H{"error": "internal error"})
}
return
}
c.JSON(500, gin.H{"error": "unknown error"})
return
}
c.JSON(200, user)
}四、配置管理
4.1 配置结构
go
package config
type Config struct {
App AppConfig
Database DatabaseConfig
Redis RedisConfig
Log LogConfig
}
type AppConfig struct {
Name string `env:"APP_NAME" default:"myapp"`
Version string `env:"APP_VERSION" default:"1.0.0"`
Port int `env:"APP_PORT" default:"8080"`
Env string `env:"APP_ENV" default:"development"`
}
type DatabaseConfig struct {
Host string `env:"DB_HOST" default:"localhost"`
Port int `env:"DB_PORT" default:"5432"`
User string `env:"DB_USER" required:"true"`
Password string `env:"DB_PASSWORD" required:"true"`
Database string `env:"DB_NAME" required:"true"`
}4.2 配置加载
go
func Load() (*Config, error) {
var cfg Config
if err := envconfig.Process("", &cfg); err != nil {
return nil, fmt.Errorf("failed to load config: %w", err)
}
// 验证配置
if err := validate.Struct(&cfg); err != nil {
return nil, fmt.Errorf("config validation failed: %w", err)
}
return &cfg, nil
}五、日志规范
5.1 结构化日志
go
package logger
import (
"go.uber.org/zap"
)
var log *zap.Logger
func Init(env string) {
var config zap.Config
if env == "production" {
config = zap.NewProductionConfig()
} else {
config = zap.NewDevelopmentConfig()
}
var err error
log, err = config.Build()
if err != nil {
panic(err)
}
}
func Info(msg string, fields ...zap.Field) {
log.Info(msg, fields...)
}
func Error(msg string, fields ...zap.Field) {
log.Error(msg, fields...)
}
func WithContext(ctx context.Context) *zap.Logger {
if traceID := ctx.Value("trace_id"); traceID != nil {
return log.With(zap.String("trace_id", traceID.(string)))
}
return log
}5.2 日志使用
go
func (s *userService) CreateUser(ctx context.Context, user *domain.User) error {
log := logger.WithContext(ctx)
log.Info("creating user",
zap.String("email", user.Email),
zap.String("name", user.Name),
)
if err := s.repo.Create(ctx, user); err != nil {
log.Error("failed to create user",
zap.Error(err),
zap.String("email", user.Email),
)
return err
}
log.Info("user created successfully",
zap.Int64("user_id", user.ID),
)
return nil
}六、测试策略
6.1 单元测试
go
func TestUserService_CreateUser(t *testing.T) {
// Mock 仓库
mockRepo := new(mockUserRepository)
service := NewUserService(mockRepo)
user := &domain.User{
Email: "test@example.com",
Name: "Test User",
}
// 设置期望
mockRepo.On("Create", mock.Anything, user).Return(nil)
// 执行
err := service.CreateUser(context.Background(), user)
// 验证
assert.NoError(t, err)
mockRepo.AssertExpectations(t)
}6.2 集成测试
go
func TestUserAPI_Integration(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
// 启动测试数据库
db := setupTestDB(t)
defer teardownTestDB(t, db)
// 创建应用
app := setupApp(db)
// 测试请求
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/users", strings.NewReader(`{
"email": "test@example.com",
"name": "Test User"
}`))
req.Header.Set("Content-Type", "application/json")
app.ServeHTTP(w, req)
assert.Equal(t, 201, w.Code)
}七、总结
| 原则 | 说明 |
|---|---|
| 分层架构 | 关注点分离,便于测试和维护 |
| 依赖注入 | 降低耦合,提高可测试性 |
| 接口隔离 | 面向接口编程,便于替换实现 |
| 错误处理 | 统一的错误类型和处理逻辑 |
| 配置管理 | 环境变量 + 默认值 |
| 结构化日志 | 便于检索和分析 |
| 测试覆盖 | 单元测试 + 集成测试 |
遵循这些原则,能帮助你构建高质量的 Go 后端应用。

