将外部错误映射到 golang 中的域错误
Mapping external errors to domain errors in golang
我有一个名为 ComputeService
的服务类型,它实现了特定的域逻辑。服务本身依赖于名为 Computer
的接口的实现,该接口具有方法 Computer.Compute(args...) (value, error)
。如图所示,Compute
本身可能 return 某些错误。
ComputeService
需要使用正确的域错误代码从一组域错误中发送适当的错误,以便可以完成翻译,并且客户端也可以适当地处理错误。
我的问题是,Computer
实现应该将它们的失败包装在域错误中,还是应该 ComputeService
这样做。如果 ComputeService
是执行此操作的人,那么它将必须了解 return 由 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
的实施者应该用域错误来响应,因为它们是最接近操作的错误并且最能确定错误是什么。就像你说的那样,inComputeService
的逻辑打破了抽象。如果您需要将代码从特定 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
}
To keep internal error context, just embed the original error in the domain error
这可以使用来自 issue 29934, as detailed here.
的 Go 1.13(2019 年第 4 季度)的包装错误
err.Is():
拉斯·考克斯 mentions:
I think we all agree that strings.Contains(err.Error(), "not found")
is fragile code.
I hope we also agree that we'd prefer to see code like errors.Is(err, os.ErrNotExist)
.
But the point is that in many cases, it is essential to future evolution of a package to keep callers from depending on a particular error result satisfying errors.Is(err, os.ErrNotExist)
, even if that is the underlying cause in today's result.
It's like looking at an unexported field or comparing error text - it's a detail that might change.
And while strings.Contains
looks and is fragile, errors.Is
does not look nor should be considered fragile.
If we are to avoid it being fragile, then we need to provide a way for packages to report detail without letting clients test for it. That way is errors that can't be unwrapped.
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
Just stating what I see as the compromise solution offered by the team:
fmt.Errorf
is currently being used extensively to wrap errors and return a new (opaque) error (as in you cannot access the underlying error).
'%w
' can now be used to explicitly opt-in to return an error that can be unwrapped.
- errors is designed as a base package with no dependencies so that every package can depend on it.
- the team agrees to punt on the areas that there is broad disagreement, and want to release just enough (errors.Is, errors.As, extension to way most folks wrap errors) so folks can achieve things.
- Generics is not here yet, and we do not know when it will come: the heated discussion on that will make this one on "error 2 values" look like child's play.
errors.Is
and errors.As
are clean and concise enough to be comfortable for a long time.
Most of the contentious things have been punted to go 1.14.
Wrapf
cannot live in errors as it is a base package.
Wrapf
means team MUST decide on what happens when a nil
error is passed: Punt on it.
- Wrap may conflict with ideas being considered for localization, internationalization, etc.
ErrorFormatter
and ErrorPrinter
have not yet gotten much deeper usage and have warts. Punt.
我有一个名为 ComputeService
的服务类型,它实现了特定的域逻辑。服务本身依赖于名为 Computer
的接口的实现,该接口具有方法 Computer.Compute(args...) (value, error)
。如图所示,Compute
本身可能 return 某些错误。
ComputeService
需要使用正确的域错误代码从一组域错误中发送适当的错误,以便可以完成翻译,并且客户端也可以适当地处理错误。
我的问题是,Computer
实现应该将它们的失败包装在域错误中,还是应该 ComputeService
这样做。如果 ComputeService
是执行此操作的人,那么它将必须了解 return 由 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
的实施者应该用域错误来响应,因为它们是最接近操作的错误并且最能确定错误是什么。就像你说的那样,inComputeService
的逻辑打破了抽象。如果您需要将代码从特定 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
}
To keep internal error context, just embed the original error in the domain error
这可以使用来自 issue 29934, as detailed here.
的 Go 1.13(2019 年第 4 季度)的包装错误err.Is():
拉斯·考克斯 mentions:
I think we all agree that
strings.Contains(err.Error(), "not found")
is fragile code.I hope we also agree that we'd prefer to see code like
errors.Is(err, os.ErrNotExist)
.But the point is that in many cases, it is essential to future evolution of a package to keep callers from depending on a particular error result satisfying
errors.Is(err, os.ErrNotExist)
, even if that is the underlying cause in today's result.
It's like looking at an unexported field or comparing error text - it's a detail that might change.And while
strings.Contains
looks and is fragile,errors.Is
does not look nor should be considered fragile.
If we are to avoid it being fragile, then we need to provide a way for packages to report detail without letting clients test for it. That way is errors that can't be unwrapped.
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
Just stating what I see as the compromise solution offered by the team:
fmt.Errorf
is currently being used extensively to wrap errors and return a new (opaque) error (as in you cannot access the underlying error).
'%w
' can now be used to explicitly opt-in to return an error that can be unwrapped.- errors is designed as a base package with no dependencies so that every package can depend on it.
- the team agrees to punt on the areas that there is broad disagreement, and want to release just enough (errors.Is, errors.As, extension to way most folks wrap errors) so folks can achieve things.
- Generics is not here yet, and we do not know when it will come: the heated discussion on that will make this one on "error 2 values" look like child's play.
errors.Is
anderrors.As
are clean and concise enough to be comfortable for a long time.Most of the contentious things have been punted to go 1.14.
Wrapf
cannot live in errors as it is a base package.Wrapf
means team MUST decide on what happens when anil
error is passed: Punt on it.- Wrap may conflict with ideas being considered for localization, internationalization, etc.
ErrorFormatter
andErrorPrinter
have not yet gotten much deeper usage and have warts. Punt.