A few tips for production Golang app

Category: programming


After writing Golang code for some time, I found that Golang is a good language. Especially after coming from Python world, the inferred typing in most cases but explicit typing is really good for reading, understanding a moderate to big codebase. Exception handling requires some get-used-to but it is good enough now with error wrapping and unwrapping. Lack of generic is a pain point but not unmanageable. Language is good but human effort is still needed to make best use of it.

Go routine

Golang’s go routine is great. It is actually awesome. I like it better than await everything in Python’s case (not trying to start a war here, just personal feelings. Cause I do not like coloring my functions What Color is Your Function?). However, if panic happens in a go routine, the whole application is down. So my tip to the use of go routine in production app:

// Go starts a recoverable go routine with background context.
func Go(ctx context.Context, f func(ctx context.Context)) {
	funcName := getCallerFuncName(3)
	go func(ctx context.Context) {
		defer Recover(ctx, funcName, nil)

		f(ctx)
	}(BackgroundContext(ctx))
}

func getCallerFuncName(skip int) string {
	pc := make([]uintptr, skip+1)
	n := runtime.Callers(skip, pc)
	frames := runtime.CallersFrames(pc[:n])
	if frames == nil {
		return ""
	}
	frame, _ := frames.Next()
	return frame.Function
}

func Recover(ctx context.Context, name string, retErr *error) {
    // TODO: some logging and metrics here needed
	err := recover()
	if err == nil {
		return
	}
}

type backgroundContext struct {
	context.Context
}

// BackgroundContext converts context to a background context which is never canceled and has no deadline.
func BackgroundContext(ctx context.Context) context.Context {
	return &backgroundContext{ctx}
}

func (ctx *backgroundContext) Deadline() (deadline time.Time, ok bool) { return time.Time{}, false }
func (ctx *backgroundContext) Done() <-chan struct{}                   { return nil }
func (ctx *backgroundContext) Err() error                              { return nil }

See an example on Golang playground

For loop with pointer

Let’s see an example first:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, playground")
	t := []int{1, 2, 3, 4, 5}
	a := []*int{}
	for _, v := range t {
		a = append(a, &v)
	}
	for _, v := range a {
		fmt.Println(*v)
	}	
}

What do you think the stdout will be like? 1 - 5? See here. The answer is 5,5,5,5,5 – because variable created in for loop declaration use the same address – but value the is different. And to achieve what we intend, it is very easy, create a local variable inside that loop.

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, playground")
	t := []int{1, 2, 3, 4, 5}
	a := []*int{}
	for _, v := range t {
        v2 := v  // this creates a local variable whose address will be different each loop
		a = append(a, &v2)
	}
	for _, v := range a {
		fmt.Println(*v)
	}	
}

Return struct for interface type

Again let us see an example first:

package main

import "fmt"

type S struct{}

type I interface{}

func newI1() I {
	return nil
}

func newI2() I {
	var t *S
	return t
}

func main() {
	i1 := newI1()
	i2 := newI2()
	if i1 != nil {
		fmt.Println(i1)
	} else {
		fmt.Println("i1 is nil")
	}
	if i2 != nil {
		fmt.Println(i2)
	} else {
		fmt.Println("i2 is nil")
	}
}

See the result here. newI2 is clearly returning a nil right? But why the return value is not a nil? Because from type of *S to I there is a conversion and then the returned value has “iface” whose data pointer is pointing to nil.

Anyway, this is not following good practices in the first place: accepts interface but returning struct.

To be continued with more tips while I am working with Golang. :)