跳到主要内容

项目结构

问题

Go 项目应该怎么组织目录结构?

答案

标准项目布局

myproject/
├── cmd/ # 可执行文件入口
│ ├── server/
│ │ └── main.go # HTTP/gRPC 服务
│ └── worker/
│ └── main.go # 后台任务
├── internal/ # 私有包(不可被外部导入)
│ ├── handler/ # HTTP 处理器
│ ├── service/ # 业务逻辑
│ ├── repository/ # 数据访问
│ └── model/ # 数据模型
├── pkg/ # 可被外部导入的公共包
│ └── util/
├── api/ # API 定义(proto、OpenAPI)
│ └── proto/
├── configs/ # 配置文件
├── scripts/ # 脚本
├── deployments/ # 部署配置(Dockerfile、K8s)
├── docs/ # 文档
├── test/ # 集成测试
├── go.mod
├── go.sum
├── Makefile
└── README.md

关键目录说明

目录说明
cmd/每个子目录一个 main.go,代码尽量少,调用 internal
internal/Go 编译器强制:外部模块无法导入此目录下的包
pkg/可选,放可复用的公共包
api/Protobuf、OpenAPI、GraphQL Schema

分层架构

// internal/handler/user.go — 接收请求,调用 Service
type UserHandler struct {
svc *service.UserService
}

func (h *UserHandler) GetUser(c *gin.Context) {
user, err := h.svc.GetByID(c, c.Param("id"))
// ...
}

// internal/service/user.go — 业务逻辑
type UserService struct {
repo repository.UserRepository
}

func (s *UserService) GetByID(ctx context.Context, id string) (*model.User, error) {
return s.repo.FindByID(ctx, id)
}

// internal/repository/user.go — 数据访问
type UserRepository interface {
FindByID(ctx context.Context, id string) (*model.User, error)
}

小项目简化

不是所有项目都需要完整分层。小项目可以更扁平:

myapp/
├── main.go
├── handler.go
├── service.go
├── model.go
├── go.mod
└── Makefile
原则
  • 小项目:先扁平化,长大了再分层
  • 大项目:按业务域分包(user/order/),而非按类型分(handler/service/

常见面试问题

Q1: internal/pkg/ 的区别?

答案

  • internal/:Go 编译器强制私有,外部模块无法 import
  • pkg/:约定俗成的公共包目录,任何人都可以 import

Q2: Go 项目需要 DDD 吗?

答案:看项目规模:

  • 小项目:Handler → Service → Repository 三层足够
  • 复杂业务:DDD 的 Domain Layer 可以更好地组织业务逻辑
  • Go 社区没有 Spring 那样的 DDD 框架,通常手动组织

相关链接