Building Microservices in Go: REST APIs: Implementing and Dealing with errors
Tags: go, microservices
Published: 2021-07-08 20:12:00


When building any software, specifically Microservices, there's something we should always be aware of, something that is going to happen no matter what: **errors**.

Failures are always something we have to consider when building new software products, it's _part of the territory_  and there's no way around it, specially when building distributed systems.

The problem **is not failing** but rather the lack of planning regarding monitoring and reacting to those failures.

## Introduction to errors

Errors in Go are simple in a sense any type implementing the [error interface](https://blog.golang.org/error-handling-and-go#TOC_2.) is considered an error, the idea with errors is to detect them, do something with them and if needed _bubble them up_ so the callers can also do something with them:

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

In [Go 1.13](https://golang.org/doc/go1.13#error_wrapping) a few extra methods were added to the `errors` package to handle identifying and working errors in a better way, specifically:

* [errors.Is](https://golang.org/pkg/errors/#Is)

Instead of comparing a sentinel error using the `==` operator we can use something like:

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

* [errors.As](https://golang.org/pkg/errors/#As)

Instead of explicitly do the type assertion we can use this function:

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

var e *os.PathError // After
if errors.As(err, &e)
```

* New `fmt` verb `%w` and [errors.Unwrap](https://golang.org/pkg/errors/#Unwrap), with the idea of decorating errors with more details but still keeping the original error intact. For example:

```go
fmt.Errorf("something failed: %w", err)
```

This `errors.Unwrap` function is going to make more sense when looking at the code implemented below.

## Implementing a custom error type with state

> The code used for this post is [available on Github](https://github.com/MarioCarrion/todo-api-microservice-example/tree/acbed018c6abb716cd84dffc1777f91dfb5e3dfa).

[Our code](https://github.com/MarioCarrion/todo-api-microservice-example/blob/acbed018c6abb716cd84dffc1777f91dfb5e3dfa/internal/errors.go) implements an error type called `internal.Error`, this new type includes state, the idea of this state is to define an _Error Code_ that we can use to properly render different responses on our HTTP layer. Those different responses are going to be determined by the _code_ that is included in the error.

It looks like this:

```go
// 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
}
```

And the supported error codes:

```go
const (
	ErrorCodeUnknown ErrorCode = iota
	ErrorCodeNotFound
	ErrorCodeInvalidArgument
)
```

With those types we can define a few extra functions to help us wrap the original errors, for example our [PostgreSQL repository](https://github.com/MarioCarrion/todo-api-microservice-example/blob/acbed018c6abb716cd84dffc1777f91dfb5e3dfa/internal/postgresql/task.go), uses `WrapErrorf` [to wrap the error](https://github.com/MarioCarrion/todo-api-microservice-example/blob/acbed018c6abb716cd84dffc1777f91dfb5e3dfa/internal/postgresql/task.go#L38) and add extra details regarding what happend:

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

Then if this error happens, the HTTP layer can react to it and [render a corresponding response](https://github.com/MarioCarrion/todo-api-microservice-example/blob/acbed018c6abb716cd84dffc1777f91dfb5e3dfa/internal/rest/rest.go#L16-L35) with the right status code:

```go
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)
}
```

## Conclusion

The idea of defining your own errors is to consolidate different ways to handle them, adding state to them allows us to react differently; in our case it would be about rendering different responses depending on the code; but maybe in your use case it could main triggering different alerts or sending messages to different services.