关于 golang-architect
golang-architect 是用于规划 Go 软件架构的技能,涵盖服务、命令行工具、库、数据管道等项目类型。它依据需求与权衡提供结构、依赖、测试和生产就绪方面的指导。
主要功能
- 01按项目需求选择架构
- 02Go 模块与包结构指导
- 03依赖和接口设计
- 04错误处理与测试策略
- 05生产就绪检查清单
- 06SQLC 初始化脚本
适用场景
- 01为新的 Go 服务选择架构
- 02组织 Go 命令行工具或库的包
- 03规划接口与依赖边界
- 04制定测试和生产就绪实践
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tomlord1122/ tomtom-skill --skill golang-architect安装后在 Claude Code 中输入/golang-architect确认已加载。
附带文件scripts/sqlc-init.sh
详细介绍
Go 软件架构师
使用 Go 的软件架构师。不局限于后端服务——涵盖任何类型的 Go 项目:HTTP/gRPC 服务、CLI 工具、共享库、基础设施工具、数据管道、嵌入式系统 Agent 或分布式系统。重点是在 Go 的惯用风格中做出合理的架构决策。
核心理念
架构关乎权衡,而非最佳实践。每种“最佳实践”都包含一种权衡——这项技能帮助用户看清权衡,并自行做出决定。
原则:
- Go 崇尚简洁。合适的架构是能够满足实际需求的最简单架构。
- 从问题出发,而不是从模式出发。不要把整洁架构套用到只有 200 行代码的 CLI 工具上。
- Go 的优势(并发、快速编译、单一二进制文件、显式错误处理)应该塑造架构,而不是成为需要绕过的限制。
internal/包和接口系统是 Go 的主要架构工具——在采用框架之前先使用它们。
思考过程
步骤 1:了解项目(我们要构建什么?)
目标: 在选择任何模式之前,充分了解项目是什么、谁会使用它,以及有哪些约束。
需要询问的关键问题:
- 这是什么类型的 Go 项目?
- HTTP/gRPC 服务
- CLI 工具
- 共享库 / SDK
- 基础设施工具(运算符、控制器、Agent)
- 数据管道 / 流处理器
- 分布式系统组件
- 使用者是谁?(最终用户、其他服务、导入某个包的其他开发者)
- 预期生命周期是多长?(原型、生产服务、长期运行的基础设施)
- 团队规模和 Go 经验水平如何?
- 有哪些硬性约束?(延迟预算、内存限制、部署环境、合规要求)
操作:
- 确定项目类型——这决定了哪些架构模式可能适用
- 找出核心领域:这个项目封装的关键逻辑是什么?
- 梳理外部依赖:数据库、API、消息队列、文件系统、云服务
- 明确非功能性需求:延迟、吞吐量、可用性、二进制文件大小
决策点: 你可以这样描述:
- “这是一个 [类型] 项目,为 [受众] 实现 [功能],受 [Y] 约束”
步骤 2:架构选择(哪种模式合适?)
目标: 根据项目类型和复杂度选择合适的架构。过度设计和设计不足一样糟糕。
思考框架——让架构匹配项目:
| 项目类型 | 复杂度 | 推荐架构 |
|---|---|---|
| 简单 CLI 工具 | 低 | 单个 main.go + 少量包,扁平结构 |
| 带子命令的中型 CLI | 中 | 每个子命令对应一个 cmd/,共享 internal/ 包 |
| 简单 CRUD API | 低-中 | 标准分层架构(Handler → Service → Repository) |
| 包含业务逻辑的复杂服务 | 高 | 整洁架构 / 六边形架构 |
| 库 / SDK | 任意 | 面向包的设计,依赖最少,公共 API 清晰 |
| Kubernetes 运算符 / 控制器 | 中-高 | controller-runtime 模式、协调循环 |
| 数据管道 | 中 | 采用阶段、通道和上下文取消机制的管道模式 |
| 分布式系统组件 | 高 | 领域驱动设计、明确的边界、事件驱动 |
简单性测试:
- “我能在 5 分钟内向新团队成员解释这种架构吗?”
- “如果移除这一层,代码会变得更简单且不损失可测试性吗?”
- “我添加这个抽象是因为现在需要,还是因为以后可能会需要?”
反模式:
- 将整洁架构应用于 CLI 工具(过度设计)
- 拥有 50+ 个端点的服务完全没有分层(设计不足)
- 在只有一个实现时就创建接口(过早抽象)
- 标准库足以满足需求时仍使用框架
决策点: 选择并说明理由:
- “我推荐采用 [X] 架构,因为 [项目特征]”
- “我特意避免采用 [Y],因为 [它会过度设计 / 无法满足需求]”
步骤 3:模块与包设计
目标: 设计 Go 模块结构——这是任何 Go 项目中最重要的架构决策。
思考框架——Go 包原则:
- 按职责划分包,而非按类型。 使用
user/,而不是models/、handlers/、services/。 internal/是架构边界。 外部使用者无法导入internal/中的代码。- 接受接口,返回结构体。 在使用接口的地方定义接口,而不是在实现接口的地方定义。
- 保持
main.go精简。 它负责将各部分连接起来(依赖注入),不包含业务逻辑。
项目结构模板:
简单 CLI:
mytool/
├── main.go # Entry point + flag parsing
├── run.go # Core logic
├── config.go # Configuration
└── go.mod
中型服务:
myservice/
├── cmd/
│ └── server/
│ └── main.go # Entry point, wiring
├── internal/
│ ├── handler/ # HTTP/gRPC handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access
│ └── config/ # Configuration
├── pkg/ # Public reusable packages (if any)
├── db/
│ ├── migrations/
│ └── queries/ # sqlc queries
├── sqlc.yaml
└── go.mod
库 / SDK:
mylib/
├── mylib.go # Public API (keep small and stable)
├── option.go # Functional options pattern
├── internal/
│ ├── parser/ # Internal implementation
│ └── transport/ # Internal implementation
├── examples/
│ └── basic/
│ └── main.go
└── go.mod
运算符 / 控制器:
myoperator/
├── cmd/
│ └── controller/
│ └── main.go
├── api/
│ └── v1/
│ └── types.go # CRD types
├── internal/
│ ├── controller/ # Reconciliation logic
│ └── webhook/ # Admission webhooks
├── config/
│ ├── crd/
│ └── rbac/
└── go.mod
决策点: 用户可以回答:
- “我知道 [X] 代码应该放在哪里,也知道为什么放在那里”
步骤 4:依赖与接口设计
目标: 设计依赖关系图,使系统具备可测试性、可组合性和可变更性。
思考框架——依赖规则:
- 内层不能了解外层
- 依赖指向内层
- 接口由使用它的层定义(而不是实现它的层)
Go 接口指南:
// GOOD: Interface defined where it's used (service layer)
// service/user.go
type UserStore interface {
GetByID(ctx context.Context, id string) (*User, error)
}
type UserService struct {
store UserStore // depends on interface, not implementation
}
// BAD: Interface defined where it's implemented (too broad, premature)
// repository/user.go
type UserRepository interface {
GetByID(ctx context.Context, id string) (*User, error)
GetByEmail(ctx context.Context, email string) (*User, error)
Create(ctx context.Context, u *User) error
Update(ctx context.Context, u *User) error
Delete(ctx context.Context, id string) error
List(ctx context.Context, offset, limit int) ([]*User, error)
}
Go 中的依赖注入(无需框架):
// main.go — the only place that knows about all concrete types
func main() {
db := postgres.Connect(cfg.DatabaseURL)
repo := repository.NewUserRepo(db)
svc := service.NewUserService(repo)
handler := handler.NewUserHandler(svc)
router := http.NewServeMux()
handler.RegisterRoutes(router)
http.ListenAndServe(":8080", router)
}
步骤 5:错误处理策略
目标: 在各层设计一致且信息充分的错误处理方式。
思考框架:
- “可能出现哪些类型的错误?”(验证失败、未找到、冲突、内部错误、超时)
- “错误应如何在各层之间传递?”
- “应该向调用方返回哪些信息,又应该记录哪些信息?”
错误传递模型:
External interface (HTTP/gRPC/CLI): User-facing messages + status codes
↑ transforms
Business logic layer: Domain-specific errors (NotFound, Conflict, Validation)
↑ wraps with context
Data/infrastructure layer: Infrastructure errors (DB timeout, network failure)
Go 错误处理模式:
// Sentinel errors for expected conditions
var ErrNotFound = errors.New("not found")
var ErrConflict = errors.New("conflict")
// Wrapping for context
return fmt.Errorf("getting user %s: %w", id, err)
// Checking with errors.Is / errors.As
if errors.Is(err, ErrNotFound) {
// handle not found
}
第 6 步:测试策略
目标: 从一开始就为可测试性做好设计,而不是事后补救。
按项目类型划分的测试:
| 项目类型 | 单元测试 | 集成测试 | 端到端测试 |
|---|---|---|---|
| CLI 工具 | 核心逻辑函数 | 使用固件执行命令 | 完整二进制文件调用 |
| HTTP 服务 | 使用模拟依赖项测试服务层 | 使用测试数据库测试仓储层 | 使用 HTTP 客户端访问测试服务 |
| 库 | 公共 API 行为 | 不适用 | 从使用者角度进行测试 |
| Operator | Reconciler 逻辑 | 使用伪 API 服务端进行 envtest | Kind 集群测试 |
Go 测试原则:
- 对具有超过 2 种情形的函数使用表驱动测试
- 使用
testdata/目录存放固件 - 白盒测试将
_test.go文件放在同一包中;黑盒测试使用_test包 - 对互不依赖的测试使用
t.Parallel() - 在测试辅助工具中使用
t.Helper() - 始终在 CI 中运行
go test -race
第 7 步:生产就绪性
目标: 确保项目已准备好投入实际使用。
生产就绪检查清单(适用于所有 Go 项目类型):
- 配置: 使用环境变量或标志,而不是硬编码值
- 日志记录: 使用结构化日志记录(slog 或 zerolog),而不是 fmt.Println
- 上下文: 所有长时间运行的操作都接受
context.Context,以便取消 - 优雅关停: 处理 SIGTERM,排空连接,并完成正在处理的工作
- 健康检查: 对于服务,提供存活和就绪端点
- 指标: 对于服务,提供 Prometheus 指标或等效指标
- 构建: 可复现构建,并包含版本信息(
-ldflags) - CI:
go vet、staticcheck、go test -race、golangci-lint
第 8 步:实施顺序
目标: 提供清晰的实施顺序。
通用顺序(根据项目类型调整):
- 定义模块结构和
go.mod - 定义核心领域类型和接口
- 实现内层(业务逻辑/核心算法)
- 实现外层(HTTP 处理器、CLI 命令、数据访问)
- 在
main.go中将所有内容连接起来 - 在各层添加测试
- 添加生产环境相关内容(日志记录、指标、优雅关停)
- 编写文档(README、godoc,适用时包括 OpenAPI)
用法
初始化 SQLC(适用于可访问数据库的项目)
bash /mnt/skills/user/golang-architect/scripts/sqlc-init.sh [project-dir] [db-engine]
参数:
project-dir- 项目目录(默认:当前目录)db-engine- 数据库引擎:postgresql、mysql、sqlite3(默认:postgresql)
示例:
bash /mnt/skills/user/golang-architect/scripts/sqlc-init.sh
bash /mnt/skills/user/golang-architect/scripts/sqlc-init.sh ./my-project postgresql
文档资源
Context7 库 ID: /websites/gin-gonic_en(117 个代码片段,得分:90.8)
官方文档:
- Go:
https://go.dev/doc/ - Effective Go:
https://go.dev/doc/effective_go - Gin:
https://gin-gonic.com/en/docs/ - sqlc:
https://docs.sqlc.dev/ - cobra(CLI):
https://cobra.dev/ - controller-runtime:
https://pkg.go.dev/sigs.k8s.io/controller-runtime
向用户展示结果
提供 Go 架构方案时:
- 让建议的复杂度与项目复杂度相匹配
- 遵循 Go 约定(Effective Go、uber-go/guide)
- 使用依赖注入以便于测试,但不要使用框架
- 提供完整的错误处理示例
- 展示用于取消操作的上下文传递
- 适当时展示相应的测试
故障排除
“sqlc generate 失败”
- 确认查询中的 SQL 语法与数据库引擎匹配
- 检查架构是否符合查询的预期
- 运行
sqlc vet查看详细错误
“循环导入”
- 这表明架构存在问题——这些包之间的依赖关系错综复杂
- 将共享的接口或类型提取到单独的包中
- 如果这两个包本就应该属于一起,也可以将它们合并
“包太多”
- Go 包应该有实际意义,而不应只是文件夹
- 如果一个包只有一个文件,可以考虑将其与使用它的包合并
- 如果每个函数都在自己的包中,说明结构划分得过于细致
“上下文已取消”
- 在所有层之间传递上下文
- 检查是否存在没有超时设置的长时间运行操作
- 确保 goroutine 遵循上下文取消信号