mcpField
G
golang-architect

golang-architect

github.com/tomlord1122开发0382 次安装1 天前更新
代码仓库

关于 golang-architect

golang-architect 是用于规划 Go 软件架构的技能,涵盖服务、命令行工具、库、数据管道等项目类型。它依据需求与权衡提供结构、依赖、测试和生产就绪方面的指导。

主要功能

  1. 01按项目需求选择架构
  2. 02Go 模块与包结构指导
  3. 03依赖和接口设计
  4. 04错误处理与测试策略
  5. 05生产就绪检查清单
  6. 06SQLC 初始化脚本

适用场景

  1. 01为新的 Go 服务选择架构
  2. 02组织 Go 命令行工具或库的包
  3. 03规划接口与依赖边界
  4. 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 经验水平如何?
  • 有哪些硬性约束?(延迟预算、内存限制、部署环境、合规要求)

操作:

  1. 确定项目类型——这决定了哪些架构模式可能适用
  2. 找出核心领域:这个项目封装的关键逻辑是什么?
  3. 梳理外部依赖:数据库、API、消息队列、文件系统、云服务
  4. 明确非功能性需求:延迟、吞吐量、可用性、二进制文件大小

决策点: 你可以这样描述:

  • “这是一个 [类型] 项目,为 [受众] 实现 [功能],受 [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 行为不适用从使用者角度进行测试
OperatorReconciler 逻辑使用伪 API 服务端进行 envtestKind 集群测试

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 步:实施顺序

目标: 提供清晰的实施顺序。

通用顺序(根据项目类型调整):

  1. 定义模块结构和 go.mod
  2. 定义核心领域类型和接口
  3. 实现内层(业务逻辑/核心算法)
  4. 实现外层(HTTP 处理器、CLI 命令、数据访问)
  5. 在 main.go 中将所有内容连接起来
  6. 在各层添加测试
  7. 添加生产环境相关内容(日志记录、指标、优雅关停)
  8. 编写文档(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 遵循上下文取消信号

评论

支持 Markdown