<p>I am currently working on an app that uses the ideas of domain-driven-design. In simple terms, the app is separated into different layers with the business logic placed in the deepest layer, the domain layer.</p>
<p>The problem I am facing is that I need to return errors if an operation fails when using the domain. For example, I have a HashedPassword value object to produce password hashes (code is massively simplified):</p>
<pre><code>type HashedPassword string
func NewHashedPassword(password string) (HashedPassword, error) {
// validate password against password requirements
if len(password) < 6 {
// return an error saying the password does not meet requirements
}
hashed, err := someHashingFunction(password)
if err != nil{
return HashedPassword(""), err // hashing failed, we need to investigate
}
return HashedPassword(hashed), nil
}
</code></pre>
<p>At the moment, I've decided to use Dave Cheney's excellent <a href="https://github.com/pkg/errors">errors package</a> to annotate my errors and generate stack traces.</p>
<p>In terms of returned errors, I need to know why something failed. For example, in the above, I need to be able to differentiate between the password not meeting requirements and the hashing function failing.</p>
<p>Problem: I am finding that the approaches I have for returning errors are too unwieldy and a pain to use.</p>
<p>So far, I've tried 2 solutions:</p>
<ol>
<li><p>Use error codes:</p>
<pre><code>const (
HashingFailed = "HASHING_FAILED"
PasswordRequirementsNotMet = "PASSWORD_REQUIREMENTS_NOT_MET"
)
type Error struct {
Err error
Type string
}
type ErrorTyper interface {
ErrorType() string
}
func (e Error) ErrorType() string {
return e.Type
}
func (e Error) Cause() error {
return errors.Cause(e.Err)
}
func (e Error) Error() string {
return e.Err.Error()
}
func ErrorType(err error) string {
if err == nil {
return ""
}
if et, ok := err.(ErrorTyper); ok {
return et.ErrorType()
}
return ""
}
// Use it like so:
return Error{pkgErrors.New("the password does not meet the strength requirements"), PasswordRequirementsNotMet}
</code></pre></li>
</ol>
<p>Having to update and use the constants is pretty quite annoying. Also, it's possible that an error type is returned in different places, so writing the message each time is super annoying. It also does not support the case where we might might to provide more information then an error code.</p>
<ol>
<li><p>Error types:</p>
<pre><code>type SomeOtherError struct {
Err error
SomeField string-
}
func (s *SomeOtherError) Cause() error {
return s.Err
}
type PasswordRequirementsNotMetError struct {}
func (e *PasswordRequirementsNotMetError) Error () string{
return "the password did not meet the requirements"
}
// Use it like so:
return &SomeOtherError{pkgErrors.Wrap(err, "something happened"), "some string"}
return pkgErrors.WithStack(PasswordRequirementsNotMetError{})
</code></pre></li>
</ol>
<p>This is much more easier to use and the code is also easier to read. However, if I want to get the stack trace of the errors, it becomes annoying:</p>
<ul>
<li><p>If the returned error was <code>SomeOtherError</code>, I need to use <code>fmt.Printf("%+v", pkgErrors.Cause(err))</code>.</p></li>
<li><p>If the returned error was <code>PasswordRequirementsNotMetError</code>, I need to use <code>fmt.Println("%+v", err)</code>.</p></li>
</ul>
<p>Having to do this is also pretty annoying and I feel like I am fighting against the language.</p>
<p>Any ideas what I should be doing here?</p>
<hr/>**评论:**<br/><br/>schumacherfm: <pre><p>Dave also provides the solution to your problem: <a href="https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully" rel="nofollow">https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully</a></p>
<p>Create different error types with an attached behaviour. Then you can check for that behaviour and the error text itself can be anything and can change anytime. You also get the stack traces and no need to call Cause().</p></pre>F21Global: <pre><p>I did try and model the problem using the behavioral approach, but I'd still have the same problem when trying to print the stack trace.</p>
<p>In addition, I feel that as the error types would be part of the domain, and it should not be part of the domain to determine if something should be retryable, etc. I just want to let the caller know that a type of error happened, then they can make decisions about the next step and maybe pull more information out of the error to construct an error message.</p></pre>shovelpost: <pre><p>Another good solution is the way <a href="https://github.com/upspin/upspin/blob/master/errors/errors.go" rel="nofollow">Upspin</a> is doing errors.</p></pre>Ploobers: <pre><p>We're using gRPC error types to classify our errors
<a href="https://godoc.org/google.golang.org/grpc/codes" rel="nofollow">https://godoc.org/google.golang.org/grpc/codes</a></p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传