How to gracefully handle errors in web service - go

I am writing a simple REST API in go using gin. I have read many posts and texts about making error handling less repetitive in go, but I cannot seem to wrap my mind around how to do it in gin handlers.
All my service does is run some queries against a database and return the results as JSON, so a typical handler looks like this
func DeleteAPI(c *gin.Context) {
var db = c.MustGet("db").(*sql.DB)
query := "DELETE FROM table WHERE some condition"
tx, err := db.Begin()
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
defer tx.Rollback()
result, err := tx.Exec(query)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
num, err := result.RowsAffected()
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
err = tx.Commit()
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"deleted": num})
}
As you can see, even this simple handler repeats the same "if err != nil" pattern four times. In a "select" based APIs I have twice as many, since there are potential errors when binding the input data and errors when marshaling the response into JSON. Is there a good way to make this more DRY?

My normal approach is to use a wrapping function. This has the advantage (over Adrian's answer--which is also a good one, BTW) of leaving the error handling in a more Go-idiomatic form (of return result, err, as opposed to littering your code with handleError(err) type calls), while still consolidating it to one location.
func DeleteAPI(c *gin.Context) {
num, err := deleteAPI(c)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"deleted": num})
}
func deleteAPI(c *gin.Context) (int, error) {
var db = c.MustGet("db").(*sql.DB)
query := "DELETE FROM table WHERE some condition"
tx, err := db.Begin()
if err != nil {
return 0, err
}
defer tx.Rollback()
result, err := tx.Exec(query)
if err != nil {
return 0, err
}
num, err := result.RowsAffected()
if err != nil {
return 0, err
}
err = tx.Commit()
if err != nil {
return 0, err
}
return num, nil
}
For me (and generally, for Go coders), the priority is code readability over DRY. And of the three options (your original, Adrian's, and mine), in my opinion, my version is more readable, for the simple reason that errors are handled in an entirely idiomatic way, and they bubble to the top handler. This same approach works equally as well if your controller ends up calling other functions that return errors. By moving all error handling to the topmost function, you're free from error-handling clutter (other than the simple 'if err != nil { return err }` construct) throughout all the rest of your code.
It's also worth noting that this approach can be powerfuly combined with Adrian's, especially for use with multiple handlers, by changing the "wrapping" function as so:
func DeleteAPI(c *gin.Context) {
result, err := deleteAPI(c)
if handleError(c, err) {
return
}
c.JSON(200, gin.H{"deleted": num})
}

You can make it slightly more DRY with a helper:
func handleError(c *gin.Context, err error) bool {
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return true
}
return false
}
Used as:
err = tx.Commit()
if handleError(c,err) {
return
}
This only cuts the error handling line count from 4 lines to 3, but it does abstract away the repeated logic, allowing you to change the repeated error handling in one place instead of everywhere an error is handled (e.g. if you want to add error logging, or change the error response, etc.)

Related

What do empty returns in the main function mean?

I copy-pasted the code from an API (https://api.magiceden.dev/). This code gets the link and prints a slice. Here's the code:
func main() {
url := "https://api-mainnet.magiceden.dev/v2/wallets/6xX3z7uxTNB68izZW2GHKnzno49dizqeVVc5ncVzdjFM/activities?offset=0&limit=100"
method := "GET"
client := &http.Client{}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
return
}
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
I'm new to Go, and I know about empty return statements in other functions, but what is returned in main function? That's the question and I still haven't found the answer.
I tried googling it, but I couldn't find any info or examples of empty return statements in main function.
When there is no return type in the function signature the return in such a function just stops the processing of the function at this point. No further statement are run then, but the registered defer functions are processed in the reverse order they have been registered.

Is there a good way to do cache saves in goroutine?

Let's say I have a handler that makes a request and gets the latest data on the selected stock:
func (ss *stockService) GetStockInfo(ctx *gin.Context) {
code := ctx.Param("symbol")
ss.logger.Info("code", code)
url := fmt.Sprintf("URL/%v", code)
ss.logger.Info(url)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
errs.HTTPErrorResponse(ctx, &ss.logger, errs.New(errs.Internal, err))
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
errs.HTTPErrorResponse(ctx, &ss.logger, errs.New(errs.Internal, err))
return
}
defer resp.Body.Close()
var chart ChartResponse
err = json.NewDecoder(resp.Body).Decode(&chart)
if err != nil {
errs.HTTPErrorResponse(ctx, &ss.logger, errs.New(errs.Internal, err))
return
}
ctx.JSON(http.StatusOK, chart)
}
And I want to add caching here. Since I don't have a lot of experience right now, I'm interested in proper interaction with the cache.
I think that if, for example, it is not possible to save to the cache for some reason, then you can simply make a request to the api. Then I wonder if it would be right to save to the cache in a separate goroutine and immediately return the response:
func (ss *stockService) GetStockInfo(ctx *gin.Context) {
code := ctx.Param("symbol")
stockInfo, err := ss.cache.Get(code)
if err == nil {
// FIND
...
ctx.JSON(http.StatusOK, chart)
} else {
ss.logger.Info("code", code)
url := fmt.Sprintf("URL/%v", code)
ss.logger.Info(url)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
...
err = json.NewDecoder(resp.Body).Decode(&chart)
// IS IT A GOOD WAY ?
go ss.cache.Save(code,chart,expireAt)
ctx.JSON(http.StatusOK, chart)
}
}
I use redis as a cache.
I will be glad if someone says what is wrong with this approach.

How to write clean code without all these cascading error Christmas trees? [duplicate]

This question already has answers here:
Go Error Handling Techniques [closed]
(11 answers)
Closed 2 years ago.
I wrote a function that should do a simple thing:
look up for a specific address in a table and return the ID, if
already existing
if not, create a new record for this particular address
return the ID of this newly created record
As RDMS I use mysql here. I put everything in a transaction to avoid race conditions in my concurrent go-routines that makes calls to this function.
However, the tons of constant checks for err makes the code ugly and full test coverage hard to get.
Is there anything I can improve here in terms of better code quality?
func getAddressId(db *sql.DB, address string) (int64, error) {
tx, err := db.Begin()
if err != nil {
tx.Rollback()
return 0, err
}
stmt, err := tx.Prepare("SELECT id FROM address WHERE `address`=?")
if err != nil {
tx.Rollback()
return 0, err
}
defer stmt.Close()
var result sql.NullInt64
err = stmt.QueryRow(address).Scan(&result)
if err != nil && err != sql.ErrNoRows {
tx.Rollback()
return 0, err
}
if result.Valid {
tx.Commit()
return result.Int64, nil
}
stmt, err = tx.Prepare("INSERT INTO address (address) VALUES (?)")
if err != nil {
tx.Rollback()
return 0, err
}
var res sql.Result = nil
res, err = stmt.Exec(address)
if err != nil {
tx.Rollback()
return 0, err
}
tx.Commit()
var id int64 = 0
id, err = res.LastInsertId()
return id, err
}
First, and most importantly, there's very little wrong with the above code. There are a few pieces I'd adjust (and will below), but generally it is very clear, straightforward, and (almost) hard to get wrong. There is nothing ugly about that.
Second, see Error Handling and Go for thoughts on error handling Go, though I won't be using those techniques here because they're not necessary.
Now there is one thing that's a bit bad, which is that it's easy to forget to call tx.Rollback() or tx.Commit() in the right places. In my opinion, that's reasonable to fix (but it's really more style than substance). The below isn't tested.
// Name your return values so that we can use bare returns.
func getAddressId(db *sql.DB, address string) (id int64, err error) {
tx, err := db.Begin()
if err != nil {
return // This is a bare return. No need to write "0, err" everywhere.
}
// From this point on, if we exit with an error, then rollback, otherwise commit.
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
stmt, err := tx.Prepare("SELECT id FROM address WHERE `address`=?")
if err != nil {
return
}
defer stmt.Close() // I'm not sure this is correct, because you reuse stmt
// This is purely style, but you can tighten up `err = ...; if err` logic like this:
var result sql.NullInt64
if err = stmt.QueryRow(address).Scan(&result); err != nil && err != sql.ErrNoRows {
return
}
if result.Valid {
id = result.Int64
return
}
if stmt, err = tx.Prepare("INSERT INTO address (address) VALUES (?)"); err != nil {
return
}
res, err := stmt.Exec(address)
if err != nil {
return
}
id = res.LastInsertId()
}
That said, I think this function is doing way too much, and if you break it up, it becomes easier to understand. For example (again, untested):
func getExistingAddressId(tx *sql.Tx, address string) (id int64, err error) {
stmt, err := tx.Prepare("SELECT id FROM address WHERE `address`=?")
if err != nil {
return
}
// I believe you need to close both statements, and splitting it up makes that clearer
defer stmt.Close()
var result sql.NullInt64
if err = stmt.QueryRow(address).Scan(&result); err != nil && err != sql.ErrNoRows {
return
}
// This is probably over-complicated. If !Valid, then .Int64 is 0.
if result.Valid {
return result.Int64, nil
}
return 0, nil
}
func insertNewAddress(tx *sql.Tx, address string) (id int64, err error) {
stmt, err := tx.Prepare("INSERT INTO address (address) VALUES (?)")
if err != nil {
return
}
defer stmt.Close()
res, err := stmt.Exec(address)
if err != nil {
return
}
return res.LastInsertId()
}
func getAddressId(db *sql.DB, address string) (id int64, err error) {
tx, err := db.Begin()
if err != nil {
return
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
if id, err = getExistingAddressId(tx, address); err != nil || id != 0 {
return
}
return insertNewAddress(tx, address)
}
Using named return values like this is a matter of style, and you could certainly not do it that way and it would be just as clear. But the point (a) defer is a powerful way to avoid duplicating logic that must always run and (b) if a function becomes a mess of error handling, it probably is doing too much.
As a side note, I strongly suspect you could get rid of the Prepare calls here, would would simplify things significantly. You only use the Statements one time. If you cached that Statements and reused them, then it would make sense to Prepare them. If you do that, then the code simplifies to:
func getExistingAddressId(tx *sql.Tx, address string) (int64, error) {
var result sql.NullInt64
if err := tx.QueryRow("SELECT id FROM address WHERE `address`=?", address).
Scan(&result); err != nil && err != sql.ErrNoRows {
return 0, err
}
return result.Int64, nil
}
func insertNewAddress(tx *sql.Tx, address string) (int64, error) {
res, err := tx.Exec("INSERT INTO address (address) VALUES (?)", address)
if err != nil {
return 0, err
}
return res.LastInsertId()
}
func getAddressId(db *sql.DB, address string) (id int64, err error) {
tx, err := db.Begin()
if err != nil {
return 0, err
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
if id, err = getExistingAddressId(tx, address); err != nil || id != 0 {
return
}
return insertNewAddress(tx, address)
}
Rather than trying to simplify Go syntax, this simplifies the operation, which as a side effect makes the syntax simpler.
A small subtlety that may go overlooked if you're not very familiar with named return values. In return insertNewAddress(...), the return value of the function call gets assigned to id and err before the defer runs, so the if err != nil check will correctly reflect the returned value. This can be a bit tricky, so you may prefer to write this all more explicitly, especially now that the function is so much shorter.
func getAddressId(db *sql.DB, address string) (int64, error) {
tx, err := db.Begin()
if err != nil {
return 0, err
}
var id Int64
id, err = getExistingAddressId(tx, address)
if err == nil && id == 0 {
id, err = insertNewAddress(tx, address)
}
if err != nil {
tx.Rollback()
return 0, err
}
tx.Commit()
return id, nil
}
And now the code is very straightforward, with no tricks, which IMO is Go at its best.

Close/return ResponseWriter from child function in Go

I am new to Go and building web apps. An example of my handler is something like this:
func getAllPostsHandler(w http.ResponseWriter, r *http.Request) {
var posts []Post
dbSesstion := context.Get(r, "database").(*mgo.Session)
err := dbSesstion.DB(dbsett.Name).C(dbsett.Collection).Find(nil).All(&posts)
if err != nil {
log.Print("error: ", nil)
w.WriteHeader(http.StatusInternalServerError)
return
}
err = json.NewEncoder(w).Encode(posts)
if err != nil {
log.Print("error: ", nil)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
My handlers have a lot of repeating error checking like this:
if err != nil {
log.Print("error: ", nil)
w.WriteHeader(http.StatusInternalServerError)
return
}
I want to make a function, which checks for error, print logs, write the response writer if neccessary, and return, but not only to the handler, but to stop all other response writings and return the handler itself. Is it possible to do so? I am thinking about panicing, but something tells me that its not the right way.
You can't escape outermost function from innermost but you can at least compact code a bit by inverting control flow
err := dbSesstion.DB(dbsett.Name).C(dbsett.Collection).Find(nil).All(&posts)
if err == nil {
err = json.NewEncoder(w).Encode(posts)
if err == nil {
err = somethingelse()
if err == nil {
w.WriteHeader(http.StatusOK)
return //successfully
}
}
}
log.Print("error: ", nil)
w.WriteHeader(http.StatusInternalServerError)

Whats the correct Go way to do handle errors

This seems a bit stupid, surely theres a better way?
err = SendMessageAndWait(db, "this is a test")
if err != nil {
fmt.Println("Error sending message", err)
return
}
err = DoSomething(db, "this is a test")
if err != nil {
fmt.Println("Error sending message", err)
return
}
err = CheckSomething(db, "this is another test")
if err != nil {
fmt.Println("Error sending message", err)
return
}
err = SendMessageAndWait(db, "this is a third test")
if err != nil {
fmt.Println("Error sending message", err)
return
}
... x10 ...
Update: For the record, 5 years on from when I wrote this, I am now persuaded that this is a completely sufficient, and perhaps even better, way to handle errors clearly. Not saying its pretty though.
Sadly that's the way it is in Go, however in a way you can make it cleaner:
func isError(err error, pre string) error {
if err != nil {
log.Printf("%v: %v", pre, err)
}
return err
}
func isErrorBool(err error, pre string) (b bool) {
if err != nil {
log.Printf("%v: %v", pre, err)
b = true
}
return
}
func checkSomething() error {
return nil
}
func main() {
if err := isError(checkSomething(), "something failed"); err != nil {
return /* err */
}
//if you don't want to return the error, just check it and die.
if isErrorBool(checkSomething(), "something else failed") {
return
}
}
I would not just print an error and return nothing: the idea is to act on the error and return it (if no decisive action was taken, like a simple log).
Simply calling return is like ignoring the error completely as far as the rest of the application is concerned.
See "Best Practices for Errors in Go", which includes advices as:
Predefine errors
Given a small set of errors, the best way to handle this is to predefine each error publicly at the package level.
Provide information
custom error type is the best solution to this problem. Go's implicit interfaces make creating one easy
Provide stack traces
package errgo provides the functionality of wrapping an error into another one that records where the error happened.
(You have the same features in dropbox/godropbox/errors/errors.go)
In Go, always check for errors. For example,
package main
import "fmt"
func doStuff() error {
err := SendMessageAndWait(db, "this is a test")
if err != nil {
return err
}
err = DoSomething(db, "this is a test")
if err != nil {
return err
}
err = CheckSomething(db, "this is another test")
if err != nil {
return err
}
err = SendMessageAndWait(db, "this is a third test")
if err != nil {
return err
}
return nil
}
func main() {
err := doStuff()
if err != nil {
fmt.Println("Error sending message", err)
}
}
Given your lack of context, I can only assume you're returning from func main().
http://play.golang.org/p/pgcwMb647A
package main
import (
"fmt"
"log"
"errors"
)
func foo(x int) error {
if x == 3 {
return errors.New("x == 3")
}
fmt.Println(x)
return nil
}
func main() {
check := func(err error) {
if err != nil {
log.Fatal(err)
}
}
check(foo(1))
check(foo(2))
check(foo(3))
check(foo(4))
}
Generally, explicit handling is the way to go, but there's a variety of things you can do depending on the context.
At the risk of turning this into code golf, Go supports single line if statements with assignments in them:
if err := SendMessageAndWait(db, "this is a test"); err != nil {
return err
}
The downside is that all return values assigned are scoped to the corresponding if/else if/else block, so if you actually need a different returned value outside that block, you have to go with something closer to PeterSO's answer.

Resources