4. 基础项目结构:创建 Go Module、定义包结构、引入依赖管理
在开始编写断路器状态机与压测代码之前,必须先搭建一个清晰、可扩展的项目骨架。本章将引导你完成从零初始化 Go Module、划分核心包目录、以及引入关键依赖的全过程。一个良好的项目结构不仅能提升代码的可维护性,还能让后续的单元测试、基准测试和集成压测更加顺畅。
4.1 初始化 Go Module
项目根目录命名为 circuit-breaker-demo,使用 Go 1.21+ 版本。执行以下命令创建 Module:
mkdir circuit-breaker-demo
cd circuit-breaker-demo
go mod init github.com/yourname/circuit-breaker-demo
初始化完成后,根目录会生成 go.mod 文件。建议在 go.mod 中显式指定 Go 版本,并开启 toolchain 管理:
module github.com/yourname/circuit-breaker-demo
go 1.21
toolchain go1.21.5
命名规范: Module 路径建议使用完整的 GitHub 仓库路径,这样后续引入内部子包时路径清晰,也方便开源发布。
4.2 包结构设计
采用扁平化 + 领域分层的策略,将断路器核心逻辑、状态机、压测工具、测试用例分别放入独立包中。整体结构如下:
circuit-breaker-demo/
├── go.mod
├── go.sum
├── cmd/
│ └── bench/ # 压测入口
│ └── main.go
├── internal/
│ ├── breaker/ # 断路器核心接口与实现
│ │ ├── breaker.go
│ │ ├── state.go
│ │ └── state_machine.go
│ ├── config/ # 配置定义
│ │ └── config.go
│ └── metrics/ # 指标收集
│ └── metrics.go
├── pkg/
│ └── testutil/ # 测试辅助工具
│ ├── mock_breaker.go
│ └── clock.go
└── test/
└── integration/ # 集成压测
└── bench_test.go
| 目录/包 | 职责 | 是否对外暴露 |
|---|---|---|
cmd/bench |
压测程序入口,解析参数、启动压测循环 | 否(仅 main 包) |
internal/breaker |
断路器状态机核心逻辑:Closed、Open、HalfOpen 状态转换 | 否(internal 限制) |
internal/config |
断路器阈值、超时、滑动窗口等配置结构体 | 否 |
internal/metrics |
请求计数、成功/失败率、熔断次数等指标收集 | 否 |
pkg/testutil |
Mock 断路器、可控时钟、模拟调用函数 | 是(可被外部测试引用) |
test/integration |
集成压测用例,使用 testing 包 + 自定义压测循环 |
否 |
为什么用 internal? Go 的
internal 包机制可以强制限制外部项目直接引用断路器核心实现,只暴露 pkg/testutil 中的测试辅助工具,符合最小暴露原则。
4.3 核心包内部依赖关系
下图展示了各包之间的依赖流向,确保依赖方向从底层向高层单向流动,避免循环依赖:
4.4 引入依赖管理
本项目核心依赖分为三类:断路器实现(自研)、压测工具、测试断言库。使用 go get 添加依赖:
# 压测工具:go-wrk 或自定义压测框架(此处使用标准 testing 包 + 手动压测循环)
# 无需额外依赖,Go 标准库 testing 已满足需求
# 如果需要更丰富的断言,可引入 testify
go get github.com/stretchr/testify
# 如果需要模拟时间,可引入 benbjohnson/clock
go get github.com/benbjohnson/clock
执行后 go.mod 会自动更新依赖版本并生成 go.sum。建议在开发阶段锁定依赖版本:
go mod tidy # 清理无用依赖
go mod verify # 校验依赖完整性
| 依赖包 | 用途 | 是否必须 |
|---|---|---|
testing(标准库) |
单元测试、基准测试、压测循环 | 是 |
stretchr/testify |
断言、Mock、Suite 组织 | 推荐 |
benbjohnson/clock |
可控时钟,用于状态机超时测试 | 推荐 |
golang.org/x/sync |
并发原语(如 ErrGroup、Semaphore) | 可选 |
注意: 不要过度引入外部依赖。断路器核心逻辑应尽量使用标准库实现,仅在测试辅助和并发控制场景引入轻量级第三方包。过多的依赖会增加编译时间并引入潜在的不兼容风险。
4.5 包初始化与编译验证
在 internal/breaker/breaker.go 中编写一个空壳接口,验证项目能否编译通过:
package breaker
// CircuitBreaker 定义断路器核心接口
type CircuitBreaker interface {
// Allow 检查是否允许请求通过
Allow() bool
// Success 记录成功调用
Success()
// Failure 记录失败调用
Failure()
// State 返回当前状态
State() State
}
在 internal/breaker/state.go 中定义状态枚举:
package breaker
// State 表示断路器状态
type State int
const (
StateClosed State = iota // 闭合状态,正常放行
StateOpen // 断开状态,快速失败
StateHalfOpen // 半开状态,试探恢复
)
func (s State) String() string {
switch s {
case StateClosed:
return "CLOSED"
case StateOpen:
return "OPEN"
case StateHalfOpen:
return "HALF_OPEN"
default:
return "UNKNOWN"
}
}
在根目录执行编译验证:
go build ./...
go vet ./...
如果输出为空,说明项目结构正确、依赖完整、代码无编译错误。此时基础项目骨架搭建完成,可以进入下一阶段的状态机实现。
最佳实践: 每次新增包或修改依赖后,都执行
go mod tidy 和 go vet,确保 go.mod 和 go.sum 保持干净,避免 CI 流水线中出现意外错误。