mcpField
G
go-project-layout

go-project-layout

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

关于 go-project-layout

go-project-layout 是面向 Go 开发者和编码 Agent 的项目结构技能,根据规模与类型提供布局建议,并涵盖包边界、依赖方向、配置和验证。

主要功能

  1. 01按项目规模选择布局
  2. 02模块与包命名规则
  3. 03服务和库的目录结构
  4. 04依赖方向原则
  5. 05配置与验证清单

适用场景

  1. 01搭建小型 Go 工具
  2. 02规划 Go 服务结构
  3. 03组织可导入的 Go 库
  4. 04确定模块边界

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-project-layout

安装后在 Claude Code 中输入/go-project-layout确认已加载。

详细介绍

Go 项目布局

结构应与项目规模相匹配。Go 项目中最常见的布局错误,是为只有 500 行代码的工具照搬微服务骨架,或者把一个包含 50 个包的服务都塞进扁平目录。布局应与项目相匹配。

社区默认规则。若项目技能或 Agent 配置规则明确取代了 go-project-layout,则以该规则为准。

1. 根据项目规模选择布局

项目布局
小型工具、单个二进制文件、少于 5 个文件扁平布局:所有内容都放在根目录的 main 包中
供他人导入的库根包以模块命名,辅助代码放在 internal/ 中
只有一个二进制文件的服务cmd/<name>/main.go + internal/ 包
多个共享代码的二进制文件cmd/<name1>/、cmd/<name2>/ + internal/

切勿为了“以后用得上”而预先创建空的 pkg/、api/、docs/、build/ 目录。应在代码需要时再添加结构,而不是提前添加。

2. 模块命名

# ✅ Good — repository path, lowercase
go mod init github.com/acme/payment-service

# ❌ Bad — not fetchable, uppercase, or vanity without DNS
go mod init PaymentService
go mod init payment_service

路径的最后一部分应与用户看到的名称一致:对于库,它会成为默认的导入名。

3. 服务布局(API 和工作进程的默认布局)

payment-service/
├── cmd/
│   └── payment-api/
│       └── main.go         # flag/env parsing, wiring, Run() — nothing else
├── internal/
│   ├── domain/             # core types, business rules; zero external deps
│   ├── service/            # use cases orchestrating domain + stores
│   ├── store/              # data access implementations (postgres/, redis/)
│   ├── handler/            # HTTP/gRPC adapters
│   └── config/             # config loading and validation
├── migrations/             # if the service owns a database
├── go.mod
├── Makefile
└── README.md

规则:

  • 默认使用 internal/——编译器会确保模块外部的代码无法导入其中的内容。只有在确有需要时才将其提升为公开包。
  • 仅当存在外部使用方且模块也包含私有代码时才使用 pkg/。拿不准时就不要创建它。
  • 依赖关系应指向内部:handler → service → domain ← store。domain 不导入 store 或 handler。

4. 保持 main 精简,并让 Run 可运行

让 main.go 只负责组装依赖并委托调用,这样应用才能方便测试:

func main() {
    if err := run(context.Background(), os.Args[1:], os.Getenv); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func run(ctx context.Context, args []string, getenv func(string) string) error {
    cfg, err := config.Load(getenv)
    if err != nil {
        return fmt.Errorf("load config: %w", err)
    }

    db, err := store.Open(ctx, cfg.DatabaseURL)
    if err != nil {
        return fmt.Errorf("open db: %w", err)
    }
    defer db.Close()

    svc := service.New(store.NewUserRepo(db))
    srv := handler.NewServer(cfg.Addr, svc)
    return srv.ListenAndServe(ctx)
}
  • os.Exit 只出现一次,且位于 main 中。
  • run 接收其依赖项(args、getenv),以便测试调用它。
  • 不要使用 init() 函数组装依赖——只采用显式的构造顺序。

5. 库的布局

retry/
├── retry.go            # package retry — the API, in the root
├── retry_test.go
├── backoff.go          # same package, split by topic
├── internal/
│   └── clock/          # implementation details users must not import
├── examples_test.go    # Example* functions shown in godoc
└── go.mod
  • 根目录本身就是包。不要使用 src/ 或 lib/。
  • 每个概念对应一个包。避免使用 util、common、helpers 这类名称——应按包所提供的功能命名(retry、clock、httpsign)。

6. 目录和包的命名规则

  • 包名应与目录名一致,简短、小写且不带下划线:使用 store/postgres,而不是 store/postgres_impl。
  • 不要重复表达:使用 payment.Service,而不是 payment.PaymentService。
  • cmd/ 中的二进制名称面向用户:cmd/payment-api;目录名可使用连字符(目录中只放 main 包)。

7. 应放在根目录的文件

  • go.mod、go.sum、README.md、LICENSE、Makefile、.golangci.yml、Dockerfile(单二进制项目)。
  • 请勿创建:src/(不符合惯例)、vendor/(除非团队明确要求将依赖 vendored)、以及诸如 types/ 或 models/ 这类最终会变成杂物堆的单文件包。

8. 配置

  • 使用一个配置结构体,在一个位置(internal/config)从环境中加载,并在启动时校验——一次列出所有缺失或无效的值,而不是只报告第一个。
  • CLI 使用标志;服务使用环境变量(12 因素);默认值写在代码中。不要将配置文件打包进镜像,也不要在业务代码中编写针对特定环境的分支(if env == "prod")。
  • 密钥应来自环境或密钥管理器,绝不能来自标志(ps 会显示 argv)或代码仓库。

9. 使用一个模块还是多个模块

默认每个代码仓库使用一个模块。只有在不同部分需要独立进行版本管理和发布时才拆分模块(例如,服务旁边的公开客户端库)。使用 go.work 在本地跨模块开发;不要提交 replace => ../path 指令。

脚手架搭建流程

  1. 提出问题并作出决定:这是工具、库还是服务?有多少个二进制文件?
  2. 执行 go mod init <repo-path>,然后有意设置 go 指令 — 从 Go 1.26 起,go mod init 写入的是上一版本 (go 1.(N-1).0),而不是工具链的版本。
  3. 只创建首个功能需要的目录。
  4. 按照上文的精简主函数模式编写 main.go,并创建配置结构体。
  5. 将开发工具固定为模块工具(Go 1.24+ 使用 go get -tool ...),并在 Makefile 中添加 build、test、lint 目标。
  6. 验证:脚手架上的 go build ./... 和 go vet ./... 均能通过。

验证清单

  1. 目录布局与项目规模相符 — 不要创建空的脚手架目录
  2. 模块路径是可获取的仓库路径
  3. 所有非公开包都位于 internal/ 下
  4. main.go 保持精简:解析、组装、调用 run、退出
  5. os.Exit 只在 main 中使用;不在 init() 中组装依赖
  6. 依赖向内流动;domain 不导入任何基础设施包
  7. 不要创建 util/common/helpers/models 这类杂项包
  8. 包名与目录名一致,使用小写,避免名称重复
  9. 新建的脚手架上 go build ./... 能通过
  10. 在启动时于单一位置加载并验证配置;不要在标志中放入密钥
  11. 除非各部分独立发布,否则只使用一个模块;不要提交本地 replace

评论

支持 Markdown