Go REST API 项目标准目录结构指南

3次阅读

Go REST API 项目标准目录结构指南

本文介绍 go 语言中构建 restful api 的推荐项目组织方式,强调符合 go 习惯的扁平化、职责清晰、可测试性强的包结构,避免过度模仿 rails 等框架的 mvc 分层,同时兼顾可维护性与工程扩展性。

在 Go 生态中,「约定优于配置」并不等同于 Rails 式的严格目录规范;相反,Go 社区推崇显式、简洁、面向包(package)而非层级的设计哲学。一个健康的 rest api 项目应以功能边界和依赖关系为驱动,而非机械套用 MVC 模块划分。以下是经过生产验证的推荐结构(以模块化、可伸缩为前提):

myapi/ ├── cmd/ │   └── myapi/              # 主程序入口(仅含 main.go) ├── internal/               # 应用核心逻辑(外部不可导入) │   ├── handler/            # http 请求处理器(对应路由终点) │   ├── service/            # 业务逻辑层(协调 model、repo、外部服务) │   ├── model/              # 数据结构定义(DTO、领域实体、请求/响应体) │   └── repo/               # 数据访问层(数据库、缓存、第三方 API 封装) ├── pkg/                    # 可复用的通用组件(如日志、中间件、工具函数) ├── api/                    # OpenAPI/Swagger 定义(可选,但强烈推荐) ├── migrations/             # 数据库迁移脚本(如使用 gormigrate 或 goose) ├── go.mod └── go.sum

✅ 关键设计原则说明

  • cmd/ 下只放 main.go:负责初始化依赖(如 DB 连接、配置加载)、组装 handler 并启动 HTTP server。它应极度轻量,不包含任何业务逻辑。
  • internal/ 是核心隔离区:所有应用专有代码置于其中,天然阻止外部模块直接 import,保障封装性与演进自由度。
  • handler 不等于“控制器”:每个 handler 函数应仅做三件事——解析请求、调用 service、构造响应。例如:
    // internal/handler/user_handler.go func CreateUser(w http.ResponseWriter, r *http.Request) {     var req CreateUserRequest     if err := json.NewDecoder(r.Body).Decode(&req); err != nil {         http.Error(w, "Invalid JSON", http.StatusBadRequest)         return     }     user, err := service.CreateUser(r.Context(), req.Name, req.Email)     if err != nil {         http.Error(w, err.Error(), http.StatusInternalServerError)         return     }     w.Header().Set("Content-Type", "application/json")     json.NewEncoder(w).Encode(user) }
  • service 层承担编排职责:它依赖 repo 和 model,但不依赖 handler 或 http;这使得业务逻辑可被 CLI、gRPC、定时任务等任意入口复用。
  • 避免 models/ 目录泛滥:Go 中“model”不是 ORM 映射类集合,而是语义明确的结构体(如 User, Order),建议按领域聚合在 internal/model/,而非拆分为 user_model.go/order_model.go 等碎片文件。

⚠️ 常见误区与提醒

  • ❌ 不要为“看起来像 Rails”而创建 controllers/、views/、helpers/ 目录——Go 没有视图层,controller 概念由 handler + service 协同替代;
  • ❌ 避免过早引入全框架(如 Revel):其 MVC 抽象会掩盖 Go 的并发模型与错误处理本质,增加学习成本与调试难度;
  • ✅ 路由推荐使用 net/http 原生 ServeMux 或轻量库(如 gorilla/mux, chi),保持路由注册逻辑集中且显式:
    // cmd/myapi/main.go r := chi.NewRouter() r.Post("/users", handler.CreateUser) r.Get("/users/{id}", handler.GetUser) http.ListenAndServe(":8080", r)
  • ✅ 配置、日志、指标等横切关注点应通过依赖注入(如结构体字段)传递至 service/repo,而非全局变量或单例。

最终,优秀的 Go 项目结构不是一成不变的模板,而是随着业务复杂度自然生长的结果:从 cmd/ + internal/handler/ + internal/model/ 的极简起步,当 service 层逻辑膨胀时再拆分子包(如 service/user/, service/payment/),始终让包名反映其责任,让 import 图呈现清晰的单向依赖流。

text=ZqhQzanResources