Go中的微服务:实施和处理错误

66 阅读3分钟

在构建任何软件,特别是微服务时,我们应该始终注意一些事情,这些事情无论如何都会发生:错误

在构建新的软件产品时,失败始终是我们必须考虑的问题,这是领域的一部分,没有办法绕过它,特别是在构建分布式系统时。

问题不在于失败,而在于缺乏对这些失败的监控和反应的计划。


错误介绍

Go中的错误很简单,从某种意义上说,任何实现了错误接口的类型都被认为是错误,错误的概念是检测它们,对它们做一些处理,如果需要的话,把它们泡起来,以便调用者也能对它们做一些处理。

if err := function(); err != nil {
  // something happens
  return err
}

Go 1.13中,一些额外的方法被添加到errors 包中,以更好的方式处理识别和处理错误,特别是。

而不是使用== 操作符来比较一个哨兵错误,我们可以使用类似的方法。

if err == io.ErrUnexpectedEOF // Before
if errors.Is(err, io.ErrUnexpectedEOF) // After

而不是显式地做类型断言,我们可以使用这个函数。

if e, ok := err.(*os.PathError); ok // Before

var e *os.PathError // After
if errors.As(err, &e)
  • fmt 动词%werrors.Unwrap,其目的是用更多的细节来装饰错误,但仍然保持原始错误的完整性。比如说。
fmt.Errorf("something failed: %w", err)

当看下面实现的代码时,这个errors.Unwrap 函数会更有意义。

用状态实现一个自定义的错误类型

我们的代码实现了一个名为internal.Error 的错误类型,这个新类型包括状态,这个状态的想法是定义一个错误代码,我们可以用它来正确地在我们的HTTP层上呈现不同的响应。这些不同的响应将由包含在错误中的代码决定。

它看起来像这样。

// Error represents an error that could be wrapping another error, it includes a code for determining
// what triggered the error.
type Error struct {
	orig error
	msg  string
	code ErrorCode
}

以及支持的错误代码。

const (
	ErrorCodeUnknown ErrorCode = iota
	ErrorCodeNotFound
	ErrorCodeInvalidArgument
)

有了这些类型,我们可以定义一些额外的函数来帮助我们包装原始的错误,例如我们的PostgreSQL存储库,使用WrapErrorf 来包装错误并添加关于发生了什么的额外细节。

return internal.Task{}, internal.WrapErrorf(err, internal.ErrorCodeUnknown, "insert task")

然后,如果这个错误发生了,HTTP层可以对它做出反应,并以正确的状态代码呈现相应的响应

func renderErrorResponse(w http.ResponseWriter, msg string, err error) {
	resp := ErrorResponse{Error: msg}
	status := http.StatusInternalServerError

	var ierr *internal.Error
	if !errors.As(err, &ierr) {
		resp.Error = "internal error"
	} else {
		switch ierr.Code() {
		case internal.ErrorCodeNotFound:
			status = http.StatusNotFound
		case internal.ErrorCodeInvalidArgument:
			status = http.StatusBadRequest
		}
	}

	renderResponse(w, resp, status)
}

结论

定义你自己的错误的想法是整合不同的方法来处理它们,给它们添加状态允许我们做出不同的反应;在我们的案例中,这将是根据代码呈现不同的响应;但也许在你的用例中,它可能主要触发不同的警报或发送消息给不同的服务。