关于 go-architecture-review
用于审查 Go 项目架构、制定重构计划,或为新服务结构提供建议的技能。检查目录布局、依赖方向、装配、包设计、配置和初始化方式。
主要功能
- 01目录布局审查模式
- 02有序重构计划
- 03新服务结构指导
- 04依赖方向检查
- 05包与配置审查
- 06init 函数使用指导
适用场景
- 01审查 Go 仓库架构
- 02规划渐进式架构调整
- 03确定新 Go 服务的结构
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ eduardo-sl/ go-agent-skills --skill go-architecture-review安装后在 Claude Code 中输入/go-architecture-review确认已加载。
详细介绍
Go 架构评审
优秀的架构让下一次改动变得容易。糟糕的架构让每次改动都令人担忧。
工作模式
开始前,选择与请求匹配的模式:
- 布局评审(默认)——根据以下各节评估现有代码库,并按严重程度报告违规项。
- 重构计划——执行相同的评估,但交付内容是有序的迁移计划(先从最小且安全的步骤开始),而不只是发现的问题。
- 新服务咨询——当对方询问“X 应该如何组织”时,将第 1 至 3 节作为指导规范,而不是评审检查项。
审查大型代码库
对于包含许多包的代码库,先了解依赖关系,再进行判断:
- 绘制模块图:运行
go list ./...列出包,然后通过导入语句追踪依赖方向。 - 分别开展独立检查:(a) 根据第 1 节检查布局;(b) 根据第 2 节检查依赖方向;(c) 根据第 3 和第 5 节检查组装与配置;(d) 根据第 4 节检查包设计。
- 如果你的环境支持委派给并行子 Agent 或任务,请为每项检查分配一个;最后综合结果——依赖方面的发现往往能解释布局方面的问题。
- 每项发现都要注明包路径和
file.go:line。
1. 标准项目布局
myproject/
├── cmd/ # Main applications (one dir per binary)
│ ├── api-server/
│ │ └── main.go
│ └── worker/
│ └── main.go
├── internal/ # Private packages — cannot be imported externally
│ ├── domain/ # Core business types (entities, value objects)
│ │ ├── user.go
│ │ └── order.go
│ ├── service/ # Business logic (use cases)
│ │ ├── user.go
│ │ └── order.go
│ ├── store/ # Data access (repositories)
│ │ ├── postgres/
│ │ │ └── user.go
│ │ └── redis/
│ │ └── cache.go
│ ├── handler/ # HTTP/gRPC handlers (adapters)
│ │ └── user.go
│ └── config/ # Configuration loading
│ └── config.go
├── pkg/ # Public packages (use sparingly)
│ └── httputil/
│ └── response.go
├── migrations/ # Database migrations
├── api/ # API definitions (OpenAPI, proto files)
├── go.mod
├── go.sum
└── Makefile
关键规则:
internal/在编译器层面强制实现封装。应积极使用。pkg/用于真正可复用的包。如有疑问,使用internal/。cmd/中的主包应保持精简——组装依赖并调用Run()。- 每个二进制文件对应一个
main.go,其中的逻辑应尽量精简。
2. 依赖方向
依赖必须向内流动。领域核心不应有任何外部依赖:
handlers → services → domain ← stores
↓ ↓ ↓
(net/http) (pure Go) (database/sql)
规则:
domain/不导入项目中的任何内容。不能导入store、handler或config。service/依赖domain/中的类型和接口,而不是具体的存储实现。handler/依赖service/接口。store/实现定义在service/或domain/中的接口。- 循环依赖属于 🔴 阻断问题。编译器会发现这类问题,但设计应从源头避免它们。
// ✅ Good — service defines the interface it needs
// internal/service/user.go
type UserStore interface {
GetByID(ctx context.Context, id string) (*domain.User, error)
Create(ctx context.Context, user *domain.User) error
}
type UserService struct {
store UserStore // depends on interface, not postgres.Store
}
// internal/store/postgres/user.go
type Store struct { db *sql.DB }
// Implements service.UserStore without importing the service package
func (s *Store) GetByID(ctx context.Context, id string) (*domain.User, error) { ... }
3. main 包组装
main.go 是组合根。应在这里组装所有内容:
func main() {
cfg := config.Load()
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
db, err := sql.Open("postgres", cfg.DatabaseURL)
if err != nil {
logger.Error("connect db", slog.Any("error", err))
os.Exit(1)
}
defer db.Close()
// Wire dependencies
userStore := postgres.NewUserStore(db)
userService := service.NewUserService(userStore)
userHandler := handler.NewUserHandler(userService, logger)
// Setup router
r := chi.NewRouter()
r.Mount("/api/v1/users", userHandler.Routes())
// Run server
srv := &http.Server{Addr: cfg.Addr, Handler: r}
// ... graceful shutdown
}
避免使用依赖注入框架。Go 的显式组装是一项优势。
如果组装过程变得复杂,可使用 Google 的 wire 生成编译期 DI 代码。
4. 包设计原则
一个包 = 一个用途
// ✅ Good — clear purpose
package orderservice // business rules for orders
package postgres // PostgreSQL data access
package httphandler // HTTP transport layer
// ❌ Bad — grab-bag packages
package utils // what ISN'T a util?
package common // everything and nothing
package models // types without behavior
避免包名重复表达
// ❌ Bad — package name repeated in type
package user
type UserService struct{} // user.UserService
// ✅ Good
package user
type Service struct{} // user.Service
包的内聚性优先于大小
包含 20 个相关文件的包,胜过 20 个各自只有 1 个文件的包。 应在包职责不同的时候拆分,而不是因为包变大就拆分。
5. 配置
type Config struct {
Addr string `env:"ADDR" envDefault:":8080"`
DatabaseURL string `env:"DATABASE_URL,required"`
LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
Timeout time.Duration `env:"TIMEOUT" envDefault:"30s"`
}
规则:
- 所有配置都来自环境变量(12-factor)。
- 在启动时验证配置,并在失败时快速给出清晰的消息。
- 不要将配置散落在各个包中——统一集中到
internal/config。 - 绝不硬编码值。即使是“暂时这样”也不行。
6. Init 函数
避免使用 init()。它会隐式运行,增加测试难度,还会产生隐藏依赖。
// ❌ Bad — hidden side effects
func init() {
db, _ = sql.Open("postgres", os.Getenv("DB_URL"))
}
// ✅ Good — explicit initialization
func NewStore(dsn string) (*Store, error) {
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, fmt.Errorf("open db: %w", err)
}
return &Store{db: db}, nil
}
例外:可以在 init() 中注册驱动或编解码器:
func init() {
sql.Register("custom", &CustomDriver{})
}
架构评审清单
- 🔴 包之间不存在循环依赖
- 🔴 领域类型不依赖任何基础设施
- 🔴
cmd/主包中没有业务逻辑 - 🔴 没有包含副作用(数据库连接、HTTP 调用)的
init() - 🟡 使用
internal/存放项目私有包 - 🟡 接口定义在消费者一侧,而非生产者一侧
- 🟡 配置集中管理,并在启动时验证
- 🟡 依赖方向向内流动(处理器 → 服务 → 领域)
- 🟢 包名简短、单数且描述明确
- 🟢 不使用
utils/、common/、helpers/包 - 🟢 主包是精简的组合根
评论
支持 Markdown