架构设计

分层架构

请求 → Handler → Service → Repository → 数据库

三层各有明确职责:

  • Handler:解析 HTTP 请求(参数绑定、校验),调用 Service,返回 HTTP 响应。不包含业务逻辑。
  • Service:业务逻辑(如邮箱去重、部门存在性校验),调用 Repository 持久化。不关心 HTTP 细节。
  • Repository:数据访问,纯 GORM 操作。不包含业务判断。

接口抽象

每一层对外暴露接口,内部是私有实现:

// repository 层
type EmployeeRepository interface { ... }
func NewEmployeeRepository(db *gorm.DB) EmployeeRepository

// service 层
type EmployeeService interface { ... }
func NewEmployeeService(empRepo EmployeeRepository, deptRepo DepartmentRepository) EmployeeService

// handler 层
type EmployeeHandler struct { service EmployeeService }
func NewEmployeeHandler(s EmployeeService) *EmployeeHandler

这样做的好处:

  • 测试时可以用 mock 替换任意层
  • 替换实现(比如换数据库)不影响上层代码
  • 依赖关系清晰,不会循环引用

依赖注入

所有依赖在 main.go 中组装:

func main() {
    cfg := config.Load()
    db := config.NewDB(cfg)

    empRepo := repository.NewEmployeeRepository(db)
    deptRepo := repository.NewDepartmentRepository(db)

    empSvc := service.NewEmployeeService(empRepo, deptRepo)
    deptSvc := service.NewDepartmentService(deptRepo)

    empHandler := handler.NewEmployeeHandler(empSvc)
    deptHandler := handler.NewDepartmentHandler(deptSvc)

    r := handler.SetupRouter(empHandler, deptHandler)
    r.Run(":" + cfg.Port)
}

不使用全局变量,所有依赖通过构造函数传入。

错误处理

业务错误定义在 service/errors.go:

var (
    ErrEmailExists  = errors.New("email already exists")
    ErrDeptNotFound = errors.New("department not found")
)

Service 返回 sentinel error,Handler 用 errors.Is 判断后返回对应 HTTP 状态码。Repository 层直接返回 GORM 错误,不包装。