在Golang项目中,随着业务模块增多,错误处理往往分散在数据库、RPC和HTTP层,导致同一种错误被重复判断和转换。构建错误中间层的核心思路是在底层和业务层之间增加一层统一的错误封装与转换逻辑,让各模块只关心自身领域错误,由中间层完成上下文追加和对外形态转换。

为什么需要错误中间层
Go语言没有异常机制,错误以返回值形式传递。如果不加约束,会出现以下问题:
- 底层错误直接暴露给接口层,泄露实现细节
- 每个调用方都写一遍错误类型判断,代码重复
- 缺少统一错误码,前端难以根据类型处理
定义统一错误接口
先在中间层定义领域错误结构,携带错误码与上下文:
package errmiddle
// DomainError 统一错误结构
type DomainError struct {
Code string
Message string
Cause error
}
// Error 实现error接口
func (e *DomainError) Error() string {
if e.Cause != nil {
return e.Message + ": " + e.Cause.Error()
}
return e.Message
}
// New 创建领域错误
func New(code, msg string, cause error) *DomainError {
return &DomainError{Code: code, Message: msg, Cause: cause}
}
中间层包装函数
基础设施层返回原生错误时,由中间层转换为领域错误:
package errmiddle
import "database/sql"
// FromDB 将数据库错误转为领域错误
func FromDB(err error) error {
if err == sql.ErrNoRows {
return New("NOT_FOUND", "资源不存在", err)
}
return New("DB_ERROR", "数据库异常", err)
}
在业务中使用中间层
业务函数只调用中间层转换,不关心底层细节:
package service
import (
"ippipp.com/app/errmiddle"
"ippipp.com/app/repo"
)
func GetUser(id int) (string, error) {
u, err := repo.FindUser(id)
if err != nil {
// 统一通过中间层转换
return "", errmiddle.FromDB(err)
}
return u.Name, nil
}
接口层统一转换
HTTP中间件可识别DomainError并映射状态码:
package http
import (
"net/http"
"ippipp.com/app/errmiddle"
)
func HandleError(w http.ResponseWriter, err error) {
if de, ok := err.(*errmiddle.DomainError); ok {
switch de.Code {
case "NOT_FOUND":
w.WriteHeader(http.StatusNotFound)
default:
w.WriteHeader(http.StatusInternalServerError)
}
w.Write([]byte(de.Message))
return
}
w.WriteHeader(http.StatusInternalServerError)
}
小结
通过引入错误中间层,Golang项目可以把错误分类、包装和转换集中管理。模块只需抛出或转换领域错误,接口层统一处理,既清晰又易于扩展。