I spend a lot of time writing microservices in Go. Therefore, I found myself needing a concrete way to hand errors back to the client that was predictable and repeatable.
So I decided to follow the error response structure from https://jsonapi.org/format/#errors. This spec makes it very clear and concise to where the client can always expect a well formatted response that will never stray.
A few notable takeaways with this spec are:
- A server MAY chose to stop processing as soon as a problem is encountered.
- A server MAY chose to continue processing and encounter multiple problems.
- Error objects MUST be returned as an array keyed by errors in the top level of the json api document.
- An error object MAY have the following members:
Id: a unique identifier for this particular occurrence of the problem.Links: a links object containing the following members:
- about: a link that leads to further details about this particular occurrence of the problem.Status: the HTTP status code applicable to this problem, expressed as a string value.Code: an application-specific error code, expressed as a string value.Title: a short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization.Detail: a human-readable explanation specific to this occurrence of the problem. Like title, this field’s value can be localized.Source: an object containing references to the source of the error, optionally including any of the following members:
- pointer: a JSON Pointer [RFC6901] to the associated entity in the request document [e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute].
- parameter: a string indicating which URI query parameter caused the error.Meta: a meta object containing non-standard meta-information about the error.
This Json spec depicts how flexible errors can be and how they can be tailored to anyone's specific needs.
Now, the microservices need a dry way to always return the same error structure without much headache. Enter (shameless plug) go-jsonerror. This is a very small and lightweight go package, that I recently wrote, that handles creating your error response accordingly.
Overall, the main purpose go-jsonerror is to give you a quick way to build your error to spec and return it in either a byte or string format.
Above is a snippet of how it could be used in action. As you may notice, there are two structs this package offers.
ErrorJson struct may contain an array of ErrorComps (I will cover ErrorComp briefly) and it also has three functions:
- AddError — Since the JsonAPI spec states that the json returned must be always returned in an array even if only 1 error is present. This means that ErrorJson can also hold an array of errors (ErrorComp).
- Error — This will return your error in a json string.
- ErrorByte — This will return your error in a byte.
ErrorComp is the struct you would use to wrap your error message before giving it to the ErrorJson struct. Here are the available fields:
- ID — May be used if you want to pass along a correlation ID for tracking
- Detail — Specific information about the reason for error
- Title — Same as detail however a bit more generic
- Code — A unique error code
- Source — Returns back the URI of the request that had caused the error
- Status — Represents your HTTP status code
Note that all of these fields are optional. The ones you use are completely up to you (I did skip out on using the links & meta error members).
All in all
I wrote go-jsonerror to prevent wrapping of errors in each microservice, but instead to import it as a package. Its solution, may not be thorough, however it suits a majority of the needs to allow for consistent error responses across microservices.
The git repo can be found here: https://github.com/ddymko/go-jsonerror