Satisfying an interface with Golang - Real life example with go-aws SDK

One of the first best practices I started using when coding with Golang is have
the method arity accept the top interface and not the specific class/struct.

This means, that as long as your type satisfies a specific interface, you can
treat your go code as pretty dynamic.

I want to walk though a specific implementation of this I recently had to do
with one of my projects.

The project

Here’s the code

func (r *RDS) getLatestSnapshotId() (string, error) {
	input := &rds.DescribeDBSnapshotsInput{
		DBInstanceIdentifier: r.Config.DBName,
	}
	output, _ := r.Service.DescribeDBSnapshots(input)

	if len(output.DBSnapshots) == 0 {
		return "", fmt.Errorf("Didn't find any snapshots for %s", r.Config.DBInstanceIdentifier)
	}

  // REST OF CODE HERE
}

output here is of type DescribeDBSnapshotsOutput which contains a slice of
snapshots with a timestamp field called SnapshotCreateTime.

Implementing the sort

Diving into the sort documentation here: https://golang.org/pkg/sort/ we see that
sort.Sort accepts a data Interface which looks like this:

type Interface interface {
        // Len is the number of elements in the collection.
        Len() int
        // Less reports whether the element with
        // index i should sort before the element with index j.
        Less(i, j int) bool
        // Swap swaps the elements with indexes i and j.
        Swap(i, j int)
}

All I need to do is implement this interface on a type and pass it to
sort.Sort, lets dive into this.

I created a file called snapshotslice.go

func NewSnapshotSlice(snapshots []*rds.DBSnapshot) *SnapshotSlice {
	return &SnapshotSlice{
		Snapshots: snapshots,
	}
}

type SnapshotSlice struct {
	Snapshots []*rds.DBSnapshot
}

You can see here that SnapshotSlice holds a reference to the original
rds.DBSnapshots and that’s what we’re going to use.

First method we need to implement is Len()

func (p SnapshotSlice) Len() int {
	return len(p.Snapshots)
}

Pretty straightforward, we just “delegate” the len to the original slice.

The next is Less(), which is a bit more “complicated” but we can explain it
simply by:

The method accepts to indexes on the slice, you need to say whether i is
“bigger” than j remember these since you’ll need to swap the position later.

func (p SnapshotSlice) Less(i, j int) bool {
	t := *p.Snapshots[i].SnapshotCreateTime
	o := *p.Snapshots[j].SnapshotCreateTime
	return t.Before(o)
}

I can use t.Before(o) since the data type for that field is Time, this
saved me a parsing phase if the original library would have returned these as
strings for example.

The last method is Swap which basically swaps one with the other

func (p SnapshotSlice) Swap(i, j int) {
	p.Snapshots[i], p.Snapshots[j] = p.Snapshots[j], p.Snapshots[i]
}

Now, we can do this in our code (later in the getLatestSnapshotId method)

	snapshots := NewSnapshotSlice(output.DBSnapshots)
	sort.Sort(sort.Reverse(snapshots))

Now, I get a sorted slice which I can grab the first item and it’s the latest
snapshot.

Conclusions

Before going into Golang a lot of people have the wrong concept that because
it’s strongly typed you lose a lot of dynamic support. In a way, that’s true
but it’s still not as limiting as you think.

In order to do that, you need to adapt with methods arity that accept the
higher Interface and not the specific type.

When you use a library and it doesn’t do what you want, you can wrap the
libraries objects with your own that implement any method you want and use that
for the rest of your code.

Thank you

Hope you enjoyed reading and this helps you with your code/work.