mcpField
G
go-architecture-review

go-architecture-review

github.com/eduardo-sl开发83110 次安装2 周前更新
代码仓库

关于 go-architecture-review

用于审查 Go 项目架构、制定重构计划,或为新服务结构提供建议的技能。检查目录布局、依赖方向、装配、包设计、配置和初始化方式。

主要功能

  1. 01目录布局审查模式
  2. 02有序重构计划
  3. 03新服务结构指导
  4. 04依赖方向检查
  5. 05包与配置审查
  6. 06init 函数使用指导

适用场景

  1. 01审查 Go 仓库架构
  2. 02规划渐进式架构调整
  3. 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 节作为指导规范,而不是评审检查项。

审查大型代码库

对于包含许多包的代码库,先了解依赖关系,再进行判断:

  1. 绘制模块图:运行 go list ./... 列出包,然后通过导入语句追踪依赖方向。
  2. 分别开展独立检查:(a) 根据第 1 节检查布局;(b) 根据第 2 节检查依赖方向;(c) 根据第 3 和第 5 节检查组装与配置;(d) 根据第 4 节检查包设计。
  3. 如果你的环境支持委派给并行子 Agent 或任务,请为每项检查分配一个;最后综合结果——依赖方面的发现往往能解释布局方面的问题。
  4. 每项发现都要注明包路径和 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