【问题标题】:Mapping external errors to domain errors in golang将外部错误映射到 golang 中的域错误
【发布时间】:2019-06-21 15:52:31
【问题描述】:

我有一个名为ComputeService 的服务类型,它实现了某些域逻辑。服务本身依赖于名为Computer 的接口的实现,该接口有一个方法Computer.Compute(args...) (value, error)。如图所示,Compute 本身可能会返回某些错误。

ComputeService 需要使用正确的域错误代码从一组域错误中发送适当的错误,以便可以完成翻译并且客户端也可以适当地处理错误。

我的问题是,Computer 实现应该将它们的失败包含在域错误中,还是应该 ComputeService 这样做。如果ComputeService 是这样做的,那么它必须知道Computer 接口的不同实现返回的不同错误,我认为这会破坏抽象。两种方式都演示如下:

package arithmetic
type Computer struct {
}
func (ac Computer) Compute(args ....) (value, error) {
     // errors is a domain-errors package defined in compute service project
     return errors.NewDivideByZero()
}

package compute
type Service struct {
}
func (svc Service) Process(args...) error {
    computer := findComputerImplementation(args...)
    val, err := computer.Compute(args...)
    if err != nil {
       if err == arith.ErrDivideByZero {
          // converting an arithmetic computer implementation 
          // specific error to domain error
          return errors.NewDivideByZero()
       } else if err == algebra.ErrInvalidCoEfficient {
          // converting an algebraic computer implementation 
          // specific error to domain error
          return errors.NewBadInput()
       }
       // some new implementation was used and we have no idea
       // what errors it could be returning. so we have to send
       // a internal server error equivalent here
       return errors.NewInternalError()
    }

}

【问题讨论】:

  • 或者你可以结合:返回一个包含特定Computer特定错误的错误类型。
  • 喜欢errors.NewComputerFailure(err) ?
  • 是的......
  • 不幸的是,这是不可能的,因为至少,我的 http 处理程序需要区分客户端错误和内部错误。例如,如果 Computer 实现依赖于 db/external 服务并且失败了,这是一个内部错误。但是如果客户端发送一个需要除以零的计算请求,这是一个客户端错误。所以将所有Compute 错误映射到一个ComputeFailure 是行不通的。
  • 我不是要求它是自动的。我的问题是应该在哪里生成域错误。 Compute 实现应该返回arithalgebra 等包中定义的常量错误,ComputeService 将其映射到自己的错误类型还是Compute 实现本身返回适当的域错误?

标签: go error-handling abstraction


【解决方案1】:

Computer 的实现者应该响应域错误,因为它们是最接近操作的,并且最能确定错误是什么。就像你说的那样,将这种逻辑 in ComputeService 打破了抽象。如果您需要将代码从特定的 Computer 错误映射到域错误,请创建将主要逻辑与该错误包装代码分开的包装结构。

要保留内部错误上下文,只需将原始错误嵌入到域错误中并生成IsSpecificDomainError 助手。

type MyDomainError struct {
    Err error
}

func NewMyDomainErr(err error) error {
    return &MyDomainError{err}
}

func IsMyDomainError(e error) bool {
    _, ok := err.(*MyDomainError)
    return ok
}

【讨论】:

  • 感谢您的回答。除了最初的问题,您认为这是否也适用于 Repository 对象?例如,UserRepository.FindUserByID(uid) 应该返回 ErrNoRecordFoundvar ErrNoRecordFound = erros.New("no record") 还是应该直接返回 MyDomainError{"no such user", uid}
  • 如果你已经为 all 实现者对 ErrNoRecordFound 进行了标准化,那么我会说满足服务中的接口和解释是可以的。我想我的回答会更简单:如果包装服务需要理解它,那么它必须是已知类型,这应该被视为“实现”接口的一部分。是 ErrNoRecordFound 还是特定的域错误并不重要。
【解决方案2】:

要保留内部错误上下文,只需将原始错误嵌入域错误中

这可能会使用包装错误,这些错误即将用于 Go 1.13(2019 年第四季度),从 issue 29934detailed here

err.Is()

作为拉斯考克斯mentions

我想我们都同意strings.Contains(err.Error(), "not found") 是脆弱的代码。

我希望我们也同意我们更愿意看到像errors.Is(err, os.ErrNotExist) 这样的代码。

但关键是,在许多情况下,对于包的未来发展来说,避免调用者依赖于满足errors.Is(err, os.ErrNotExist) 的特定错误结果是必不可少的,即使这是今天的根本原因 em>的结果。
这就像查看未导出的字段或比较错误文本 - 这是一个可能会改变的细节。

虽然strings.Contains 看起来很脆弱,但errors.Is 看起来并不脆弱,也不应该被视为脆弱。
如果我们要避免它变得脆弱,那么我们需要为包提供一种报告详细信息的方法,而无需让客户对其进行测试。这种方式是无法解包的错误。

err.As()

var pe *os.PathError
if errors.As(err, &pe) {
     use(pe)
}

%w:

func inner() error { return errors.New("inner error") }
func outer() error { return fmt.Errorf("outer error: %w", inner()) }

fmt.Fprintf("%+v", outer())
// outer error:
//     /path/to/file.go:123
//   - inner error:
//     /path/to/file.go:122

current status for Go 1.13

仅说明我认为团队提供的折衷解决方案:

  • fmt.Errorf 目前被广泛用于包装错误并返回一个新的(不透明的)错误(因为您无法访问底层错误)。
    '%w' 现在可用于显式选择加入以返回可解包的错误。
  • errors 被设计为没有依赖关系的基础包,因此每个包都可以依赖它。
  • 团队同意在存在广泛分歧的领域下注,并希望发布足够多的内容(errors.Is、errors.As,扩展大多数人包装错误的方式),以便人们能够实现目标。
  • 泛型还没有出现,我们不知道它什么时候会出现:关于它的激烈讨论将使这个关于“错误 2 值”的问题看起来像儿戏。
    errors.Iserrors.As 是干净简洁,足以长时间舒适。

大多数有争议的事情都被搁置到了 1.14。

  • Wrapf 不能存在错误,因为它是一个基础包。
  • Wrapf 表示团队必须决定在传递 nil 错误时会发生什么:弃权。
  • 包装可能与本地化、国际化等考虑的想法发生冲突。
  • ErrorFormatterErrorPrinter 还没有得到更深入的使用并且有疣。平底船。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    • 1970-01-01
    • 2017-07-16
    • 1970-01-01
    • 2015-07-28
    • 1970-01-01
    • 2019-05-23
    相关资源
    最近更新 更多