架构设计
分层架构
请求 → 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 错误,不包装。