好的项目结构能让代码更易理解、易维护、易扩展。Go 社区有一套被广泛认可的标准布局。

1. 标准项目布局

参考:golang-standards/project-layout

myproject/
├── cmd/                    # 主程序入口
│   ├── server/
│   │   └── main.go        # 服务端入口
│   └── cli/
│       └── main.go        # 命令行工具入口
├── internal/              # 私有代码(不可被外部导入)
│   ├── handler/
│   ├── service/
│   └── repository/
├── pkg/                   # 公共库(可被外部导入)
│   ├── util/
│   └── validator/
├── api/                   # API 定义(OpenAPI/Swagger)
│   └── openapi.yaml
├── web/                   # Web 静态资源
│   ├── static/
│   └── templates/
├── configs/               # 配置文件
│   ├── config.yaml
│   └── config.prod.yaml
├── scripts/               # 脚本(构建、部署等)
│   ├── build.sh
│   └── deploy.sh
├── test/                  # 额外的测试数据
│   └── fixtures/
├── docs/                  # 文档
│   └── API.md
├── go.mod
├── go.sum
├── Makefile              # 构建脚本
├── Dockerfile
└── README.md

2. 核心目录详解

2.1 cmd/

存放主程序入口,每个子目录对应一个可执行文件。

cmd/
├── server/
│   └── main.go        # go build -o server ./cmd/server
├── worker/
│   └── main.go        # go build -o worker ./cmd/worker
└── migrate/
    └── main.go        # go build -o migrate ./cmd/migrate

main.go 应该尽量简洁

// cmd/server/main.go
package main

import (
    "myproject/internal/server"
)

func main() {
    server.Run()  // 具体逻辑在 internal/server 中
}

2.2 internal/

私有代码,只能被本项目导入,外部项目无法导入。

internal/
├── handler/      # HTTP 处理器
├── service/      # 业务逻辑
├── repository/   # 数据访问
├── model/        # 数据模型
└── middleware/   # 中间件

为什么需要 internal?

  • 防止外部项目依赖你的内部实现
  • 给你重构的自由(不用担心破坏外部依赖)

2.3 pkg/

公共库,可以被外部项目导入。

pkg/
├── util/         # 通用工具函数
├── validator/    # 验证器
└── logger/       # 日志封装

何时使用 pkg?

  • 代码足够通用,可以被其他项目复用
  • 已经稳定,不会频繁变动

注意:很多项目不需要 pkg/,所有代码都放在 internal/ 即可。

2.4 api/

存放 API 定义文件:

  • OpenAPI/Swagger 规范
  • Protocol Buffers (.proto 文件)
  • GraphQL schema
api/
├── openapi.yaml
├── user.proto
└── schema.graphql

2.5 configs/

配置文件模板(不包含敏感信息)。

configs/
├── config.yaml          # 开发环境配置
├── config.prod.yaml     # 生产环境配置模板
└── config.example.yaml  # 配置示例

敏感信息应该通过环境变量传入

3. 其他常见目录

3.1 build/

构建相关文件:

build/
├── package/      # 打包脚本
└── ci/           # CI 配置

3.2 deployments/

部署配置:

deployments/
├── docker-compose.yml
├── kubernetes/
│   ├── deployment.yaml
│   └── service.yaml
└── terraform/

3.3 vendor/

依赖的副本(可选):

go mod vendor

通常不需要提交到版本控制。

4. 小型项目结构

对于简单项目,可以简化:

simple-project/
├── main.go           # 单文件入口
├── handler.go        # HTTP 处理器
├── model.go          # 数据模型
├── go.mod
└── README.md

或者:

simple-project/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── handler/
│   ├── service/
│   └── model/
├── go.mod
└── README.md

5. 中型项目结构

medium-project/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── handler/
│   │   ├── user.go
│   │   └── post.go
│   ├── service/
│   │   ├── user.go
│   │   └── post.go
│   ├── repository/
│   │   ├── user.go
│   │   └── post.go
│   ├── model/
│   │   └── model.go
│   └── middleware/
│       └── auth.go
├── pkg/
│   └── response/
│       └── response.go
├── configs/
│   └── config.yaml
├── go.mod
├── Dockerfile
└── README.md

6. 大型项目结构(微服务)

large-project/
├── services/
│   ├── user-service/
│   │   ├── cmd/
│   │   ├── internal/
│   │   └── go.mod
│   ├── order-service/
│   │   ├── cmd/
│   │   ├── internal/
│   │   └── go.mod
│   └── payment-service/
│       ├── cmd/
│       ├── internal/
│       └── go.mod
├── pkg/                # 跨服务共享的代码
│   ├── logger/
│   └── config/
└── api/                # API 定义
    └── proto/

7. 最佳实践

  1. 从简单开始:不要一开始就创建复杂的目录结构
  2. 按需扩展:当代码变多时再拆分目录
  3. 优先使用 internal:除非确定要公开,否则放在 internal/
  4. 保持一致:团队内统一目录结构
  5. 文档先行:在 README 中说明项目结构

8. 常见错误

8.1 过度设计

❌ 不好:一开始就创建大量空目录
myproject/
├── cmd/
├── internal/
├── pkg/
├── api/
├── web/
├── configs/
├── scripts/
├── test/
└── docs/
✅ 好:从简单开始
myproject/
├── main.go
├── handler.go
└── go.mod

8.2 混淆 internal 和 pkg

  • internal/:私有代码,频繁变动
  • pkg/:公共库,稳定可复用

大多数项目只需要 internal/

8.3 main.go 太复杂

// ❌ 不好:main.go 包含大量业务逻辑
func main() {
    db := connectDB()
    router := setupRouter()
    // ... 100 行代码
}

// ✅ 好:main.go 只负责启动
func main() {
    server.Run()
}

9. 总结

  • cmd/:程序入口
  • internal/:私有代码(最常用)
  • pkg/:公共库(谨慎使用)
  • configs/:配置模板
  • 从简单开始,按需扩展

思考题: 为什么 Go 语言要设计 internal/ 这个特殊目录?它解决了什么问题?


相关阅读