Blog

Get Your Development Team Started With Go

Go blog

Here at DigitalOcean, Go is quickly becoming one of our favorite programming languages. After a few internal debates, I've distilled a few thoughts that I'd like to share with teams new to Go (or thinking of taking it on in the future).

Using External Code

The Go package landscape is growing every day. People are sharing high quality code that prevents you from having to reinvent the wheel. There are packages that help you with tasks that range from implementing complex algorithms and building networking services to interfacing with other low level systems through the Go C bindings.

Given what's available, it's still a challenge to locate high quality packages to help you build your projects. Through word of mouth and on social channels (e.g. Twitter), I've found special gems like go-tigertonic and testify. Yes, we could have gotten by without them – but they provide benefits we don't feel the need to replicate in-house.

There are also a few package repositories that exist; however, none of them can be considered a standard. There are announcement services like OSS Go, but they aren't helpful if you are looking for something specific.

While this remains an unsolved issue, Go has a secret weapon:

The standard library included with Go is incredibly robust, and unless you are looking for something industry specific or niche, there's a high probability the standard library has a complete solution – or the stepping stone to help you build a solution.

Integrating With External Code

As you start integrating packages written externally, you should take care to use the external packages and not become the external package. External API interfaces can change, or your team may decide that it needs to replace backend systems with something more robust. Use Go's interfaces to insulate your application from your imported package's types; in doing this, the focus will shift to fulfilling your project's needs rather than building around a core you don't own or have control over.

As an example, if you're using an external Redis client package, it exports the following:

package redis

type Redis struct {}
func (r *Redis) Get(k string) (*RedisKVPair, error) { // omited}

You could use this code in your application, but you'll run into two problems: The first problem is that you'll need a Redis server if you ever want to test code that uses this package. The second is that you won't be able to easily upgrade or swap the Redis client out if you require additional functionality.

Using Go's powerful interfaces, you start specifying the behavior you desire:

package myapp

type KeyPair struct {
  Key string
  Value []byte
}

type KeyStore interface {
  Get(k string) (*KeyPair, error)
}

Next, you can create your own type that wraps the external dependency:

package myapp

func NewRedisKeyStore() {
  return &RedisKeyStore{
    r: redis.Redis{},
  }
}

func (rks *RedisKeyStore) Get(k string) (*KeyPair, error) {
  rkvp, err := rks.r.Get(k)
  if err != nil {
    return nil, err
  }

  return &KeyPair{
    Key: rkvp.Key,
    Value: rkvp.Value,
  }, nil
}

Finally, instead of using the external Redis client explicitly, you can use your wrapper or swap it out in tests:

type MockKeyStore struct {
  Dict map[string][]byte
}

func NewMockKeyStore() {
  return &MockKeyStore{
    Dict: map[string][]byte{},
  }
}

func (mks *MockKeyStore) Get(k string) (*KeyPair, error) {
  return &KeyPair{Key: k, Value: mks.Dict[k]}, nil
}

Managing Dependencies

After a bit of time, packages you rely on will be updated to fix bugs and add new features. You will quickly learn that go get is not a robust solution for maintaining and organizing dependencies. There are two solutions here that you can try: vendoring your dependencies or using an external tool such as godep.

The team evaluated vendoring packages but found out quickly that it can be overwhelming. Hence, we are currently leaning towards using godep to manage our dependencies. It provides a method for ensuring that an explicit package is used. Keep in mind it doesn't do so in a declarative manner, so you have to make sure you have the proper version installed prior to saving with godep.

While our engineering team has a strong grasp on managing our dependencies, this too remains an unsolved problem. There still isn't a simple, Go community accepted, declarative solution that allows us to specify external dependencies and the exact versions we want to use. The community is working on it, however, and a few solutions exist for your team to evaluate at the Go Wiki Tools Page.

Writing Go

One thing your team may appreciate about Go is that it doesn't require a resource heavy IDE to be productive. You can use your already-familiar text editor and start writing code. If you do this without any research though, you will be missing out on extensions that can help your productivity. I use Sublime Text for writing Go, and the popular GoSublime plugin provides features like code completion and formatting. Other team members use Vim Go with large amounts of success.

Another tool to keep an eye on is Oracle – it provides source analysis that will aid you in navigating your projects. From evaluating expressions to understanding types and callees, Oracle integration in editors will be a huge productivity boost to developers. You can try it out today if you use Emacs or the Atom package.

Our team has settled on a single workspace with multiple repositories. As mentioned above, it's still the early days of dependency management, but since we ensure that the team works on all projects in a similar manner, multiple people are able to work with multiple projects simultaneously without any major problems.

Deploying Go

After writing your software, you'll want to run it in production. Use a tool like fpm to build debs and RPMs and deploy those to your production. You'll get the value of being able to deploy from an internal repository with an explicit version. Since Go compiles down to a single binary, there will be no dependencies to manage.

As an example, you can create a Makefile to build a deb:

VERSION=0.5.0
BUILD=$(shell git rev-list --count HEAD)

widget-dpkg:
  mkdir -p deb/widget/usr/local/bin
  cp $(GOPATH)/bin/widget  deb/widget/usr/local/bin
  fpm -s dir -t deb -n widget -v $(VERSION)-$(BUILD) -C deb/widget .

Before thinking of creating packages by hand, consider using your continuous integration system as the builder. This way you'll be guaranteed an up-to-date package after every successful build. We are currently using Drone.IO for continuous integration. We're also moving to Makefiles for automating tasks, which are well known and a great way to ensure that everyone who touches the code can test and build it.

Taking Advantage Of Go's Ecosystem

Go provides tools to make it easier to work within its ecosystem. The DO team uses golint which finds simple mistakes and ensures that everything has at least a minimal set of documentation. With godoc we have an easy to use interface for viewing the documentation for all the code that our projects contain. The Golang Nuts mailing list is an invaluable resource.

So far, our short foray into Go has been a great success. Developers not familiar with the language have ramped up and become productive quickly. The language's speed of development, coupled with its easy-to-grasp concurrency, has allowed to us to write better software in a faster manner. We experimented with Go while rewriting our Droplet Console, and that experience has given us the confidence to move forward on multiple new projects.

If your team hasn't tried Go, it should be on your short list.

Write Go For DO

As we continue to write new services, as well as rewrite old ones, we grow a deeper appreciation for the language's strength in building distributed systems. If you're a software engineer that's interested in writing Go – we are hiring.

Oh, and if you have experience writing Go, feel free to comment below and share your thoughts.