| name | golang-backend-guidelines |
| description | 指导 Go 后端功能的分层设计、领域实体建模、Service 编排、错误边界、日志与依赖方向。新增或修改 application、model/entity、infrastructure、transport、controller、repository 或 Service 代码时使用。 |
Go 后端开发规范
实现或审查 Go 后端功能时,遵循本技能的分层、领域模型和错误边界规则。与本技能配套的命名、代码风格、context、安全和 lint 规则见仓库中的相关技能链接。
核心规则
1. 模型尽可能使用充血模型
涉及业务状态和业务规则的实体应优先采用充血模型:把数据、状态变化和与自身相关的约束放进实体本身,不把有明确业务行为的实体退化成只有字段的数据库记录。对于纯 DTO、简单 CRUD 或没有业务不变量的持久化映射,不强行增加领域行为。
- DTO 只负责协议输入输出;实体负责领域状态和行为。
- 外部代码不能直接修改受业务规则保护的状态;通过实体方法完成状态变化。
- 实体方法必须校验当前状态和输入,拒绝非法转换并返回明确错误。
- 复杂跨实体流程仍由 Application Service 编排,但单个实体自身的规则不能散落在 Service 中。
- 持久化映射不应成为绕过实体行为的入口;Repository 负责读写,Service 负责用例编排,实体负责自身不变量。
示例:
type Order struct {
status OrderStatus
}
func (o *Order) Pay(payment Payment) error {
if o.status == OrderStatusCanceled {
return ErrOrderCanceled
}
if payment.Amount <= 0 {
return ErrInvalidPayment
}
o.status = OrderStatusPaid
return nil
}
func (o Order) Status() OrderStatus {
return o.status
}
调用方应使用 order.Pay(payment)、order.Cancel(reason)、order.Ship(trackingNo),而不是直接执行 order.status = ...。实体必须保证“已取消不能支付”“未付款不能发货”等不变量。
2. Service 是错误边界
Service 返回给 Controller 的错误必须是 constant/errors.go 定义的 constant.ServiceError,不能把 Repository、数据库、网络或加密库的底层错误直接返回给前端。
- 可向用户解释的输入或领域错误,映射到对应的
ServiceError,例如 constant.ErrBadRequest、constant.ErrUserNotFound。
- 不应暴露的底层错误必须先记录完整日志,再映射为
constant.ErrSystemMalfunction。
- 日志记录底层错误;响应只返回稳定的错误码和用户可见消息。
- 不要把 SQL、文件路径、密钥、堆栈、供应商错误或内部实现细节放进
ServiceError.Message。
- Service 不得直接
return err,除非 err 已明确是允许暴露的 constant.ServiceError。
- 需要判断错误类型时使用
errors.Is 或 errors.As,不要依赖字符串比较。
推荐写法:
user, err := s.userRepository().GetByUsername(ctx, request.Username)
if err != nil {
s.res.Logger().Error(ctx, "failed to get user by username", err)
return nil, constant.ErrSystemMalfunction
}
校验错误和领域错误应在 Service 边界映射;底层错误应在日志中保留诊断信息,但不越过 Service 边界。
3. 遵循项目分层和依赖方向
transport → application → infrastructure,Controller 不得直接访问 Repository。
- Transport 负责协议解析和响应适配;Application Service 负责编排用例;Repository 负责持久化;Entity 负责领域规则。
- 通过项目已有的 Resource、Logger、Validator 和 Infrastructure 接口获取横向能力,不在业务层自行初始化连接或全局资源。
- 新增功能按仓库
AGENTS.md 的标准流程注册 DTO、Repository、Service、Controller、路由和依赖注入。
4. 注释使用中文
- 所有代码注释、包注释、导出符号文档注释和业务说明注释使用中文。
- 代码标识符、API 名称、协议名称、标准库术语和包名可以保留英文。
- 注释应说明必要的意图、约束或行为,不重复显而易见的代码。
5. 正确传播 context 和日志
- 所有需要 context 的 Service、Repository 和外部调用都把
ctx context.Context 放在第一个参数。
- 沿调用链传递同一个 context,不在请求处理中创建新的
context.Background()。
- 需要通过
context.WithValue 传递请求级元数据时,key 常量统一定义在 constant/contextkey.go;禁止在各业务层重复定义 context key。
- context 只携带请求级元数据,不要用它传递普通业务参数、Service、Repository 或可选配置。
- 底层失败时在最接近业务边界的 Service 记录带上下文的结构化日志,再返回服务错误。
- 不在实体中保存
context.Context、Logger、Repository 或 HTTP 对象。
6. 实现后的检查
完成代码后至少执行:
gofmt -w <modified-go-files>
golangci-lint run ./...
go vet ./...
go test ./...
同时检查:实体状态是否只能通过行为修改、Service 是否泄露底层错误、日志是否包含足够诊断上下文,以及错误映射是否覆盖所有失败路径。
配套技能
需要更详细的充血模型和错误边界示例时,读取领域模型与错误边界参考。