Golang · project layout

How I design the layout of a Go program's code on disk

The layout of a Go application on disk is a question that almost everyone asks when starting out.

It’s a fair question - there’s no scaffolding that’s widely used, there was no guidelines for people for the first decade.

One of the biggest strengths of Go is that you can lay your project out on disk pretty much how you want to.

Unfortunately, though, that strength is its weakness, people lay their projects out on disk pretty much how they want to, leaving people new to the ecosystem scratching their heads.

The official Go blog did finally create a blog post that put some ideas out there for people to use.

Here’s howI think about the layout.

In the first place, the project, for example, has a root directory, that will have the README.md and go.mod and go.sum files in it.

myproject/
├── go.mod
├── go.sum
└── README.md

The project itself has a “core” library, which is typically the business logic for the project, so I put that at the root of the project.

The package that these files are in is going to be myproject as well.

myproject/
├── go.mod
├── go.sum
├── myproject.go
├── my_project_test.go
└── README.md

To build the project into a binary there needs to be a main package. There is a de facto standard (meaning everyone uses this standard, but it’s not an actual standard), and that is to put the main package in a directory named cmd.

myproject/
├── cmd
│   └── main.go
├── go.mod
├── go.sum
├── myproject.go
├── my_project_test.go
└── README.md

The next step in my thought process is “supporting libraries”, that is, libraries that my business logic will need in order to do its tasks.

I put these supporting libraries beneath the core library, so that all developers can see that the library at the top of the tree is supported by the libraries below.

There’s a little bit extra happening here, as a firm believer in the SOLID design principles - I create an interface that is owned by the core library that defines the contract that my datastores need to abide by to be used.

For this example the interface will be in feed.go, and the feeds directory holds the code that creates types that satisfy that interface and allow the core library to interact with the upstream data feeds (each in their own directory).

myproject/
├── cmd
│   └── main.go
├── feeds
│   ├── twitter
│   │   ├── twitter.go
│   │   └── twitter_test.go
│   └── reddit
│       ├── reddit.go
│       └── reddit_test.go
├── feed.go
├── go.mod
├── go.sum
├── myproject.go
├── my_project_test.go
└── README.md

I used to put the feed.go file, with interface definition, within the feeds directory, to show that that wasthe interface that the feeds need to satisfy in order to be used by the core library, but having the definition in the core library demonstrates that the core library owns the interface (which it should).

This should be enough to demonstrate the layout of a project, with the only other thing to mention being that if the supporting libraries themselves need supporting libraries, then those secondary support libraries should go below the libraries that they support.

Published:
comments powered by Disqus