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?
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.
The package path is where your code lives. The best example of laying out packages is the standard library. Here we can see that
time are packages we can import into our code:
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:
But this is not ideal for a few reasons:
- 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.
- 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.
- This is not go gettable!
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:
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:
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.
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
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
This is how we would use the secure library:
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
The above example would only need one line changed, and the import path would stay the same:
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.
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.