3. 分层架构基础:经典三层架构

经典三层架构是Go后端项目中最广泛使用的结构模式,它将系统划分为 Handler(接口层)、Service(业务层)、Repository(数据层) 三个垂直切面。每一层只关注本层职责,通过明确的依赖规则形成单向调用链,从而降低耦合、提升可测试性。

核心原则: 上层依赖下层,下层不感知上层。Handler 调用 Service,Service 调用 Repository,Repository 只操作数据源。任何反向依赖或跨层调用都视为架构违规。

3.1 各层职责定义

层次 别名 核心职责 典型产出
Handler 传输层 / 控制器 接收请求、参数校验、调用 Service、组装响应 HTTP handler、gRPC endpoint、DTO
Service 应用服务 / 用例 编排业务逻辑、事务管理、领域规则执行 Service 接口与实现、业务错误码
Repository 数据访问层 持久化操作、查询构造、数据源适配 Repository 接口、ORM/原生 SQL 实现
关键区分: Handler 不直接操作数据库,Service 不处理 HTTP 请求细节,Repository 不包含业务判断。每一层都保持“知识最小化”。

3.2 层间依赖规则

依赖方向严格遵循 单向依赖,且依赖的是接口而非具体实现。具体规则如下:

  • Handler → Service:Handler 持有 Service 接口,通过依赖注入获得实现。
  • Service → Repository:Service 持有 Repository 接口,不关心底层是 MySQL、Redis 还是 Mock。
  • Repository → 数据源:Repository 实现依赖具体数据库驱动或 ORM,但接口定义在领域层。
  • 禁止跨层调用:Handler 不能直接调用 Repository,Service 不能直接处理 HTTP 响应。
  • 禁止循环依赖:A 层调用 B 层,B 层绝不能反向调用 A 层。
经典三层架构依赖方向 Handler(接口层) 接收请求 · 参数校验 · 响应组装 Service(业务层) 业务编排 · 事务管理 · 领域规则 Repository(数据层) 持久化 · 查询 · 数据源适配 调用 调用 禁止反向依赖 依赖规则:Handler → Service → Repository,每层只依赖下一层的接口,不依赖具体实现

3.3 代码结构示例

以下是一个典型的Go项目目录结构,严格遵循三层分层:

project-root/
├── handler/
│   ├── user_handler.go      // 定义 UserHandler,依赖 user.Service 接口
│   └── dto/
│       └── user_dto.go      // 请求/响应结构体
├── service/
│   ├── user_service.go      // 定义 UserService 接口
│   └── impl/
│       └── user_service_impl.go  // 实现,依赖 user.Repository 接口
├── repository/
│   ├── user_repository.go   // 定义 UserRepository 接口
│   └── mysql/
│       └── user_repo_mysql.go    // MySQL 实现
└── domain/
    └── user.go              // 领域实体 User
常见陷阱: 很多团队将 Repository 直接放在 models/ 或 dao/ 包下,导致 Service 层直接依赖 ORM 模型。正确的做法是:Repository 接口定义在领域层,实现放在独立的 repository/mysql/ 包中,Service 只依赖接口。

3.4 层间数据流转

数据在层间传递时,每一层使用不同的数据结构,避免层间数据耦合:

  • Handler → Service:使用 DTO(Data Transfer Object),只包含当前用例需要的数据。
  • Service → Repository:使用领域实体或查询参数对象,不包含 HTTP 上下文。
  • Repository → Service:返回领域实体或聚合根,不返回 ORM 模型。
  • Service → Handler:返回业务结果或错误码,Handler 负责组装为响应格式。
层间数据流转与结构转换 Handler DTO → 调用参数 Service 领域实体 · 业务对象 Repository 查询参数 · 实体 DTO 实体/参数 返回实体 返回结果/错误 数据转换规则:每层只转换自己需要的数据结构,不将内部模型暴露给上层 Handler 使用 DTO,Service 使用领域实体,Repository 使用查询参数

3.5 分层架构的优势与局限

维度 优势 局限
可测试性 每层可独立 Mock 下层接口进行单元测试 层数增多时测试桩编写成本上升
可维护性 职责清晰,新人容易理解代码归属 简单 CRUD 场景下分层显得冗余
可替换性 替换数据源只需更换 Repository 实现 业务逻辑分散在 Service 层,容易膨胀
团队协作 前后端、不同模块可并行开发 跨层修改时涉及多个包,变更成本高
实践建议: 对于小型项目或微服务中的简单查询,允许 Handler 直接调用 Repository 的只读方法,但必须通过接口隔离,且业务逻辑仍保留在 Service 层。这种“简化三层”在 Go 社区中也被称为“瘦 Service 模式”。

3.6 依赖注入与层间解耦

Go 中实现层间依赖解耦的推荐方式是 构造函数注入:

// handler/user_handler.go
type UserHandler struct {
    userSvc service.UserService  // 依赖接口
}

func NewUserHandler(svc service.UserService) *UserHandler {
    return &UserHandler{userSvc: svc}
}

// service/impl/user_service_impl.go
type userServiceImpl struct {
    userRepo repository.UserRepository  // 依赖接口
}

func NewUserService(repo repository.UserRepository) service.UserService {
    return &userServiceImpl{userRepo: repo}
}

// main.go 组装
func main() {
    db := mysql.NewDB()
    userRepo := mysql.NewUserRepo(db)
    userSvc := impl.NewUserService(userRepo)
    userHdl := handler.NewUserHandler(userSvc)
    // 注册路由...
}

这种模式确保:

  • 每一层只依赖接口,不依赖具体实现类。
  • 在 main.go 或独立的 wire.go 中完成组装,层与层之间没有硬编码依赖。
  • 单元测试时可以直接传入 Mock 实现,无需启动数据库或 HTTP 服务。
进阶提示: 当项目规模增大后,建议引入 Google Wire 或 Uber Fx 等依赖注入框架,自动生成组装代码,避免手动维护 main.go 中的依赖链。