2. Go 项目结构设计原则:标准布局、分层架构与接口隔离

在构建可压测、可维护的 Go 服务时,项目结构是第一道防线。混乱的目录和耦合的依赖会让单元测试寸步难行,Mock 无从下手。本章围绕三个核心原则展开:标准项目布局、分层架构(Handler / Service / Repository)、接口隔离。这三者共同构成了依赖注入与 Mock 的物理基础。

2.1 标准 Go 项目布局(Standard Go Project Layout)

Go 社区虽然没有强制目录规范,但 golang-standards/project-layout 已成为事实标准。它通过约定目录职责,让团队成员和 CI/CD 工具能快速定位代码。

目录 / 文件 职责 是否可压测
/cmd 应用入口,每个子目录对应一个可执行文件 否(仅启动逻辑)
/internal 私有应用代码,不被外部包导入 是(核心业务逻辑)
/pkg 可被外部项目复用的公共库 是(需独立测试)
/api API 协议定义(protobuf / OpenAPI) 否(契约文件)
/configs 配置文件模板 否
/test 外部集成测试、压测脚本 是(压测入口)
压测视角: /internal 和 /pkg 是压测闭环的主战场。入口 /cmd 只做依赖组装,不包含业务逻辑,这样压测时可以直接构造 Service 层,跳过 HTTP 路由。

2.2 分层架构:Handler / Service / Repository

分层架构将代码按职责切分为三层,每层只依赖下层接口,不跨层调用。这是依赖注入能够“注入 Mock”的前提。

  • Handler(传输层):解析 HTTP / gRPC 请求,调用 Service,返回响应。不包含业务逻辑。
  • Service(业务层):实现核心业务规则,依赖 Repository 接口。是压测和 Mock 的主要目标。
  • Repository(数据层):封装数据库、缓存、外部 API 的访问。实现由具体驱动(MySQL、Redis、Mock)替换。
分层架构与依赖方向 分层架构与依赖方向(Handler → Service → Repository) Handler(传输层) 解析请求 / 调用 Service / 返回响应 Service(业务层) 核心业务规则 / 依赖 Repository 接口 Repository(数据层) 数据库 / 缓存 / 外部 API 访问 依赖方向 上层依赖下层接口 下层不依赖上层 Mock 注入点 替换 Repository 实现

2.3 接口隔离:依赖反转的基石

分层架构的“依赖方向”靠 接口隔离 来保证。Service 层不依赖具体的 Repository 实现,只依赖一个接口。这样在压测时,可以注入一个 Mock 实现,完全控制数据层的返回。

核心原则: 接口定义在 调用方(Service)所在的包,而不是实现方(Repository)的包。这避免了循环依赖,也让 Mock 不需要导入真实的数据库驱动。

2.3.1 接口定义示例

// internal/service/user.go
package service

// UserRepository 定义在 Service 层,由 Repository 层实现
type UserRepository interface {
    GetByID(ctx context.Context, id int64) (*User, error)
    Save(ctx context.Context, user *User) error
}

type UserService struct {
    repo UserRepository  // 依赖接口,而非具体实现
}

func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

2.3.2 接口隔离的压测收益

场景 无接口隔离 有接口隔离
单元测试 必须启动数据库,测试慢且不稳定 注入 Mock,毫秒级运行,100% 可控
压测准备 需要搭建真实环境,成本高 Mock 模拟极端延迟/错误,快速验证
代码变更 修改数据层可能影响业务层 接口不变,实现可独立替换
接口隔离与 Mock 注入 接口隔离:Service 依赖接口,Mock 与真实实现可互换 UserService 依赖 UserRepository 接口 UserRepository 接口 MySQLUserRepo 真实数据库实现 MockUserRepo 压测 / 单元测试用 依赖注入 在 main 或 test 中 选择具体实现注入

2.4 目录结构实战模板

结合上述原则,一个可压测的 Go 项目目录结构如下:

myproject/
├── cmd/
│   └── server/
│       └── main.go              # 依赖组装入口
├── internal/
│   ├── handler/
│   │   └── user_handler.go      # 依赖 Service 接口
│   ├── service/
│   │   ├── user.go              # 业务逻辑,依赖 Repository 接口
│   │   └── user_test.go         # 单元测试 + Mock
│   └── repository/
│       ├── mysql/
│       │   └── user_repo.go     # MySQL 实现
│       └── mock/
│           └── user_repo_mock.go # Mock 实现
├── pkg/
│   └── httputil/
│       └── response.go          # 公共工具
├── test/
│   └── benchmark/
│       └── user_bench_test.go   # 压测脚本
└── go.mod
常见误区: 不要把接口定义在 repository 包中,然后让 service 包去 import。这会导致 service 包依赖 repository 包,无法在测试中轻松替换为 Mock。正确的做法是:接口定义在 service 包,repository 包实现该接口。

2.5 本章小结

  • 标准布局 提供了团队共识的目录结构,让压测脚本和 Mock 文件有固定位置。
  • 分层架构 将 Handler / Service / Repository 职责分离,每层只依赖下层接口。
  • 接口隔离 是依赖注入的物理基础,接口定义在调用方,实现方可替换。
  • 这三者共同构建了“可 Mock、可压测、可维护”的 Go 项目骨架,后续章节的依赖注入和压测闭环将在此骨架上展开。