6.4 代码组织与分层约定

代码组织与分层约定

一眼看懂的目录

dwz-server/
├── main.go                 # 入口,嵌入 static/templates,调用 cmd.Start()
├── cmd/run.go              # 装配 Assembly + Server,启动服务
├── config/
│   ├── assembly.go         # DI 装配顺序
│   ├── server.go           # 服务启动顺序
│   └── autoload/           # 路由、依赖提供者
│       └── router.go       # 所有路由集中注册
├── app/
│   ├── controller/         # HTTP 请求处理
│   ├── service/            # 业务逻辑
│   ├── dao/                # GORM 数据访问
│   ├── model/              # GORM 实体
│   ├── dto/                # 请求/响应 DTO
│   ├── middleware/         # 认证、CORS、操作日志、安装校验
│   └── constants/          # 业务常量
├── internal/
│   ├── helper/             # 全局 Helper(DI 容器)
│   ├── interfaces/         # 接口定义(依赖倒置)
│   └── pkg/                # 缓存、数据库、http_server 实现
├── pkg/helper/             # 通用工具函数
├── templates/              # 嵌入式 HTML 模板
├── static/                 # 嵌入式后台静态资源
└── docs/                   # 仓库内 Markdown 文档

分层职责

职责 不应该做的事
Controller 解析/校验请求参数、调用 Service、组装响应 直接访问数据库;写业务规则
Service 业务逻辑、事务边界、跨 DAO 组合 解析 HTTP 参数;直接写 SQL
DAO GORM 查询封装、数据持久化 业务判断;调用其他 Service
Model GORM 实体与其方法(如 IsExpired() 持有业务状态;调用 DAO
DTO 请求/响应结构,独立于 Model 持有业务逻辑
Middleware 横切关注点:认证、日志、CORS 具体业务处理

依赖方向

Controller → Service → DAO → Model
    ↓           ↓         ↓
   DTO       Helper    GORM/DB

不要反向依赖。如果发现 DAO 需要调用 Service,通常意味着业务逻辑放错了层。

Helper 与依赖注入

全局依赖(logger、config、cache、database)通过 internal/helper/helper.go 暴露,任何层都可以调用:

import "cnb.cool/mliev/dwz/dwz-server/internal/helper"

func SomeBusinessFunc() {
    logger := helper.GetLogger()
    db := helper.GetDatabase()
    cfg := helper.GetConfig()
    // ...
}

Controller 层通过 deps *impl.HttpDeps 接收额外的 HTTP 依赖:

func (c MyController) Handler(ctx *gin.Context, deps *impl.HttpDeps) {
    svc := deps.GetShortLinkService()
    // ...
}

命名约定

  • 包名:小写单数,controller / service / dao(遵循 Go 惯例)
  • 文件名:功能名下划线,short_link_controller.go
  • 结构体:大驼峰,ShortLinkController
  • 方法:大驼峰;DTO 字段 JSON tag 使用下划线命名
  • 错误:返回 error,不要 panic