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 核心包内部依赖关系

下图展示了各包之间的依赖流向,确保依赖方向从底层向高层单向流动,避免循环依赖:

断路器项目包依赖关系图 包依赖关系(单向依赖) internal/config 阈值、超时、窗口配置 internal/metrics 计数、成功率、熔断次数 internal/breaker 状态机:Closed/Open/HalfOpen pkg/testutil Mock 断路器、可控时钟 cmd/bench 压测入口 main test/integration 集成压测用例 依赖方向:config/metrics → breaker → cmd/bench 和 test/integration;testutil 仅被测试引用

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 流水线中出现意外错误。