The proxy pattern in Go

This is a continuation of the common design patterns I found in my old code series, which I started in a previous post.

The proxy pattern is a design pattern in which a class (proxy) acts as an interface to something else. The proxy could be an interface to anything: a network connection, another class, a file, etc.

A proxy can be useful in a variety of situations:

  • A frontend for load balancing
  • Hide private infrastructure
  • Caching layer
  • etc

A good example of a proxy that can be used as a load balancer (and other purposes) is nginx or net/http/httputil (the ReverseProxy).

To illustrate how to use this pattern in Go, let’s implement a simple caching layer for files to speed up multiple reads for the same files:

 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package fileproxy

import (
  "io"
  "io/ioutil"
  "sync"
)

var mux sync.RWMutex
var cache map[string][]byte

func init() {
  cache = map[string][]byte{}
}

func NewFileProxy(name string) FileProxy {
  return &fileProxy{Name: name}
}

type FileProxy interface {
  io.Reader
}

type fileProxy struct {
  Name string
}

func (fp *fileProxy) Read(b []byte) (n int, err error) {
  mux.RLock()
  if data, ok := cache[fp.Name]; ok {
    copy(b, data)
    mux.RUnlock()
    n = len(b)
    return
  }
 
  mux.RUnlock()

  var data []byte
  data, err = ioutil.ReadFile(fp.Name)
  if err == nil {
    copy(b, data)
    mux.Lock()
    n = len(b)
    cache[fp.Name] = data
    mux.Unlock()
  }

  return
}

var _ FileProxy = &fileProxy{}

And if you’d run a simple benchmark:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func BenchmarkReadFile(b *testing.B) {
  for n := 0; n < b.N; n++ {
    _, err := ioutil.ReadFile("somefile.txt")
    if err != nil {
      b.Fatal(err)
    }
  }
}

func BenchmarkFileProxy(b *testing.B) {
  data := make([]byte, 24)
  fp := NewFileProxy("somefile.txt")

  for n := 0; n < b.N; n++ {
    _, err := fp.Read(data)
    if err != nil {
      b.Fatal(err)
    }
  }
}
1
2
BenchmarkReadFile-8        87010             13601 ns/op
BenchmarkFileProxy-8    52101362                21.9 ns/op

You can see how the proxy could improve your app performance in a similar situation.

And that’s it. I hope this example has shed some light on how the proxy pattern works and can be used to improve your code performance.

For more design pattern examples, please checkout rolandjitsu/go-design-patterns.