Code Organization - Part Two

In part one I discussed workspaces in Go. To reiterate, the best way to manage workspaces in Go is to not manage it. Create a single workspace for all your work, and do not worry about it. If you are worried about dependency management, use a tool like godep. Also, you may want to checkout gopkg.in as it looks quite promising. I hope to explore it more in a future post.

So we know that Go code is kept in a workspace, and we know that your code should live in a package which lives in the src directory of your workspace. But what is a package, and how do we organize them?

Packages

In Go, a package is simply an application or a library. You can think of it as a collection of code in a single directory. A package can have a subpackage (subdirectory), but Go still views it as independent package (net/http does exactly this). There are two important parts to think about when creating and dealing with packages, paths, and names.

Paths

The package path is where your code lives. The best example of laying out packages is the standard library. Here we can see that fmt and time are packages we can import into our code:

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("Current time: ", time.Now().Format(time.RFC1123))
}

When Go compiles the above example, it looks in the standard library for the imports first. But what happens after that? If Go can not find an import in the standard library, it will then traverse the $GOPATH/src directory looking for a path that matches your import. This is where you need to pay special attention to your package’s path.

In theory we could create any arbitrary path in $GOPATH/src and Go would be happy to import our code. For example, if we had a package in $GOPATH/src/batman, this would compile and run:

package main

import (
    "fmt"
    "batman"
)

func main() {
    fmt.Println("Batman says: ", batman.ReturnSomeCoolString())
}

But this is not ideal for a few reasons:

  1. The Go standard library might introduce a package named the same as the one we created (unlikely, but it could happen). If that were to happen, it would break our code because it would be found in the standard library first, thus skipping the package we created.
  2. It is not unique enough. Any other user in the Go ecosystem could create a package with the same name, but do something completely different. This makes sharing code difficult.
  3. This is not go gettable!

go get

The go get command is awesome. If we want to integrate some third party code into our application, we can run a single command to retrieve the code for use:

$ go get github.com/codegangsta/negroni

This will download the negroni package into its canonical path at $GOPATH/src/github.com/codegangsta/negroni. Notice that it uses the full github.com/username path. This is a best practice outlined in the Go docs. Not only is it unique to this package, but it makes the code sharable via the go get command. To use the package we just downloaded we would import it like this:

package main

import (
    "net/http"
    "github.com/codegangsta/negroni"
)

func main() {
    ...
}

Even if you do not plan on sharing your code, or even version controlling your code, use your github.com/username as the base of your package paths. Of course this does not have to be GitHub, you could use your bitbucket.org/username or any other unique endpoint. The priority here is a unique path that you can use. The added benefit is that if you decided to publish your code, it will be go gettable.

Names

There are two types of packages we can create. The first is an executable application. In order to tell Go that you want your code to be executable, we give it the package name main:

package main

// Code lives here.
...

You may have noticed that the majority of examples on this blog and the Go Tour all use package main. This is because we want to execute the code and see the output.

The second type of package is a library. This is code that can not be directly executed, but can be imported by an application. An example of this is middleware packages in web applications. I have created a package called secure which has a package name of secure:

package secure

// Secure package code lives here.
...

This is how we would use the secure library:

package main

import (
    "net/http"
    "github.com/unrolled/secure"
)

func main() {
    m := http.NewServeMux()
    m.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        w.Write([]byte("Hello world."))
    })

    s := secure.New(secure.Options{
        ContentSecurityPolicy: "default-src 'self'",
    })

    http.ListenAndServe("0.0.0.0:3000", s.Handler(m))
}

Conventionally package names are the same as the last element of their import path. In my above example the package path is github.com/unrolled/secure, and if you view the source the package name is secure. This is Go’s convention and you should try your best to follow it. But it is not a requirement. If we changed my secure package name to something different, like gowebsecurity:

package gowebsecurity

// Secure package code lives here.
...

The above example would only need one line changed, and the import path would stay the same:

...
import (
    "net/http"
    "github.com/unrolled/secure"
)
...
s := gowebsecurity.New(secure.Options{
...

This is an important concept to understand. Package paths and package names do not have any correlation. It is purely a best practice (and one you should follow). The path is simply where the code lives. When we import a package into our code, the Go compiler finds the files in the package path and loads the package name into scope and continues compiling our code.

General Tips

There are not many requirements when organizing your package, for the most part it is completely up to you. But there are a couple of things to keep in mind.

A Go package can (and probably should) have multiple files. This is great for organizing your code into logical groups. No one likes looking at a 2,000 lines of code in a single file. Just make sure your package name is the same across all the files within your package. Go will only allow you to have one name per package.

If you feel the need to have multiple package names create a subpackage, but do so sparingly. See Ben Johnson’s post for more on using subpackages sparingly. A great example of properly using a subpackage is the bson subpackage within the mgo package (MongoDB driver package). Since they created bson as a subpackage, it can be tested independently and imported by others who only need the bson functionality.

That brings me to my last point. If you create subpackages, be aware that you can not have cyclic dependencies. Anyone coming from Python will be well aware of this, but just something to keep in mind.