一般来说在我们的项目中,按照严格分层的结构划分的话,大致是 handler
、service
(DDD 中细分为 application
和 domain
),dao
。
对于 dao
层,如果出现错误,我们的处理方式是直接上抛,选择性的打日志处理错误,比如可能有些接口涉及到在 dao
层进行参数校验、手动管理事务等,需要日志记录,但错误仍然是直接上抛。
在 service
层,我们遇到错误时,分为两种情况:业务预期内的错误、非业务错误。对于前者,我们的处理方式是,预先定义各种业务错误码,在遇到时将其转换为 error 接口上抛,由 handler 转换为 resp code 返回给客户端,可选 Warn
日志(低频关键场景)。对于后者,错误通常是技术错误,不应由 service
自身处理,而是交由调用方处理,那么我们在这里是打日志+错误上抛。这里的描述也不详尽,具体的可以参考下表:
场景分类 | 处理方式 | 日志策略 | 是否向上抛 | 示例 |
---|---|---|---|---|
1. 预期内的业务错误 | 返回自定义业务错误类型 | 可选Warn日志(低频关键场景) | 是 | ErrUserNotFound |
2. 需降级的非关键错误 | 静默处理,返回默认值 | 不打日志 | 否 | 缓存失效时降级查DB |
3. 基础设施/技术错误 | 包装原始错误,添加业务上下文 | 必打Error日志 | 是 | 数据库连接失败、RPC超时 |
4. 需透传的原始错误 | 直接返回原始错误 | 不打日志 | 是 | DAO层的gorm.ErrRecordNotFound |
在 handler
层要做的事就比较简单了,如果遇到 error,通过统一的 wrap function,进行错误处理:通过 error.As
判断是否为特定业务错误,如果是,取出错误码与错误信息;如果不是,返回统一错误码与错误信息。同时这里有两种选择,返回统一错误信息+ err 中的描述信息;打印 error 信息,只返回统一错误信息。个人比较倾向后者,前者更适用于 Debug 环境。