Contents

patterns in go

In go there are some patterns that make code idomatic, these are some of my favourite…

strategy pattern

“Enables an algorithm’s behavior to be selected at runtime.” Wikipedia

This pattern allows you to pass structs, or objects around that have slightly different runtime behavior without having to worry about the difference. There’s a great talk by Dave Cheney about functions as first class citizens. This talk does a great job of explaning why and how to use functions in some places instead of interfaces, and this is closely linked to the strategy pattern.

Functions work really well with this pattern and allow different implementations to be passed in at runtime. In the following package safe, the struct Str holds a string, and implements the Stringer interface:

safe.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package safe

type mutator func(input string) string

type Str struct {
	val string
	mutate mutator
}

func (s Str) String() string {
	return s.mutate(s.val)
}

func Lower(input string) string {
    return strings.ToLower(input)
}

func Obfuscate(input string) string {
	return strings.Repeat("*", len(input))
}

func NewStr(v string, f mutator) Str {
    return Str{
        val:    v,
        mutate: f,
    }
}

Str also holds a function to define how to mutate the value when String() is called. Both safe.Lower and safe.Obfuscate satisfy the mutator function type definition, so can be used as mutators for safe.Str. safe.NewStr(...) returns a new safe.Str with a value and a mutator for printing it out.

One of these implementations or ‘strategies’ could be chosen at runtime to change the behavior of an app.

main.go

1
2
3
4
5
6
7
8
package main

func main() {
    user := safe.NewStr("[email protected]", safe.Lower)
    pass := safe.NewStr("super-secret-password", safe.Obfuscate)
    
    fmt.Printf("User: %s, Pass: %s", user, pass)
}

Here we can see that the user and pass can be passed around without having to worry about the values that they hold. They can be printed and logged with out worry as the String() method will call Str.mutate() and print out the value in a safe way. In this case; not revealing the password.

1
User: [email protected], Pass: *********************

Try it out in the go playground

middleware

There are two obvious uses of middleware in go webapps. The first is incoming, wrapping incoming requests with instrumentation or context or logging etc. There are many packages that do this, e.g. the new relic agent.

One way to achieve this is chaining the http.Handler interface. This interface has a single method ServeHTTP. Your example logging middleware could look something like:

1
2
3
4
5
6
func middlware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Logic can go here
    next.ServeHTTP(w, r)
  })
}

This returns a new http.Handler that executes custom logic before calling next.

The second is outgoing, creating an http.Transport that implements the RoundTripper interface, here you can add your middleware logic.

1
2
3
4
5
6
7
8
9
type customTransport struct{}

var _ http.RoundTripper = &customTransport{} // force customTransport to implement http.RoundTripper

// Implement RoundTripper for customTransport
func (t *customTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	// Logic goes here
	return http.DefaultTransport.RoundTrip(req)
}

Then to use it, create an http.Client that uses the new customTransport

1
2
3
http.Client{
    Transport: &customTransport,
}

struct vs. data structure

There is a big debate around whether a class or struct is a data structure, but let’s not get into that here. In go there are a number of usecases where you could use a standard library data structure but a struct is often better. Given the following JSON (which describes a meal):

1
2
3
4
5
6
7
8
9
{
    "lunch": {
        "type": "sandwich"
        "ingredients": [
            "bread",
            "cheese"
        ]
    }
}

This could be marshalled into map[string]interface{} but that’s rubbish! Instead let’s use structs and named types:

1
2
3
4
5
6
type Ingredients []string

type Lunch struct {
    Food string `json:"type"`
    Ingredients `json:"ingredients"`
}

We have given our struct some tags to describe the json field name.

Here we create a named type Ingredients which has an underlying type of []string, and use composition to put that inside a Lunch struct. Then we can take our json (with maybe came from a POST request) and unmarshal it.

1
2
lunch := new(Lunch)
json.Unmarshal(request.Body, lunch)

Sure, we could have used that map[string]interface{} from before, but you wouldn’t have the ability and knowledge of what is stored in the interfaces, and nothing to enforce that the incoming json is of a valid form. We now have the power to iterate Ingredients or access lunch.type etc.. and we know what fields could be there.

1
fmt.Println("Lunch: %s, Filling: %s", lunch.Food, lunch.Ingredients)

output:

1
Lunch: sandwich, Filling: [bread cheese]

Example in the go playground

memoisation

“Memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again” - Wikipedia

Go is really good at networking, and in distributed architectures it’s easy to end up in a situation where you need to cache the values of expensive external calls. There are obvious many libraries and existing projects taht can help to do this, but it’s always useful to understand what’s going on.

This is a very simple, rudimentary example, we have a datastore to store our cached values, and a function that knows how to execute the expensive call. (because maps are not threadsafe in go, we have a mutex)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type Store struct {
    worker func(string) string
    cacheMap map[string]string
    mux sync.Mutex
}

func (s *Store) GetExpensiveString(key string) string {
    s.mux.Lock()
    defer s.mux.Unlock()
    result, found := s.cacheMap[key]
    if !found {
        result = s.worker(key)
        s.cacheMap[key] = result
    }
    return result
}

Here we can see that if the expensive result is not in the cache then calculate it and add it to the cache. This is a contrived example but it shows how the process could work.