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)替换。
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 模拟极端延迟/错误,快速验证 |
| 代码变更 | 修改数据层可能影响业务层 | 接口不变,实现可独立替换 |
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 项目骨架,后续章节的依赖注入和压测闭环将在此骨架上展开。