Catching Up With Kat Zien on the Structure of Go Apps in 2023
Let’s say I was asked to build a Go application for a website that hosts raffles.
I might end up with a file structure that looks like this:
├── main.go ├── api │ ├── client.go │ ├── raffle.go │ └── user.go ├── database │ ├── client.go │ ├── raffle.go │ └── user.go ├── Dockerfile ├── Makefile ├── go.mod ├── go.sum
But is this correct? Or is everything I’ve written here completely wrong?
This was a common feeling among Go developers back when Kat Zien gave her 2018 GopherCon US talk How Do You Structure Your Go Apps, a brilliant primer on the subject that has been referenced many times across multiple articles covering similar subjects.
I was curious to see if anything had changed 5 years on, so I invited Kat along for an interview.
This article will feature a summation of our discussion. If you’re reading and expecting an in-depth tutorial on how to structure a Go application, this is not the right place. I’ll take no offense if you bow out here.
If you’re completely unversed in this topic, I’d recommend watching Kat’s talk and then coming back, as I view this more as a commentary featuring the author.
All Good? Good. Let’s Get Go-ing!
A case for flat structures in single and multi-file applications
Single file example
├── main.go ├── client.go ├── raffle.go ├── database.go
Surely this is too simple, right? Is there ever a case where you’d only want to use
main.go? Especially in a professional environment?
Kat disagreed. A beginner trap is feeling the need to copy what some of the bigger and more complicated services are doing off the bat. However, doing so only serves to make your own code more complicated than it needs to be.
Plus, as a beginner, starting with a single file structure and then working towards grouping code into other files is a good way to practice and get used to understanding when it’s appropriate to separate code out.
“As simple as possible, but no simpler,” is a quote attributed to Albert Einstein, used by Kat in her 2018 talk. Over the years, that’s the mindset that Kat has continued to reaffirm whenever she builds services.
Kat mentioned that for several Advent of Code challenges, she only needed
main.go and a few extra files. For the occasional simple project, why add on a more complicated boilerplate?
Structuring your packages: Updates on thoughts and trends
In this next section, I asked Kat about a few different structural trends that were within her original video and some that have emerged since.
Using cmd folders
├── cmd │ └── main.go ├── api │ ├── client.go │ ├── raffle.go │ └── user.go ├── database │ ├── client.go │ ├── raffle.go │ └── user.go
Back in 2018, this was an emerging trend that you may still see today. The idea was to put any
main.go files in the
cmd folder and all packages in the
I was aware of this idea, but hadn’t really thought about where I’d picked it up from or why it appeared in the first place.
cmd folder is where you’d put different ways to launch your application.
However, nowadays, Kat feels
cmd doesn’t really add any value, so you shouldn’t add it by default. She posed the question, “How often do you need multiple
main.go files?” In Kat’s day-to-day, she typically only needs one.
If you specifically need multiple
main.go files, then use the
cmd directory; otherwise, is it necessary?
├── main.go ├── api │ ├── client.go │ ├── raffle.go │ └── user.go │ database │ ├── client.go │ ├── raffle.go │ └── user.go
Internal and external package directories
Example of internal and external packages
├── cmd │ └── main.go ├── internal │ ├── api │ │ └── client.go ├── pkg (external) │ └──protos.go
internal directory showcases to anyone looking at your service that this code is private. External services should not import any packages within this directory.
pkg directory in this case contains packages that external services can import.
Kat’s opinion was that using internal directories isn’t always necessary. At her current place of work, everything is “internal” by default. In fact, they have a specific
proto package which is used to allow for service-to-service communication. This effectively achieves internal and external separation without using those names within the repository.
However, aspects such as this are on a company-to-company basis. If your company goes forward with using an
internal directory, there could be the argument that there really isn’t anything stopping someone from using those packages – unless it’s enforced either through an automated check or some other means.
For me, I don’t mind having a directory where I essentially put all my packages, provided the root directory contains other folders that are not Go packages.
├── main.go ├── internal │ ├── api │ │ └── client.go ├── config │ └──creds.yml
In the above example, the
config directory doesn’t have a Go file. So, if I was scanning the repository, I might think there is a config Go package if the
internal directory didn’t exist.
It’s quite a minor thing, but again there are many ways to paint a cat.
A brief note on hexagonal architecture
If you ended up watching Kat’s talk before landing here, there’s one small regret she had with the final presentation. Back then, Kat didn’t marshal between layers.
├── main.go ├── api │ └── client.go ├── domain │ ├── raffle.go │ └── user.go ├── database │ └── client.go
Using my example, instead of the
api package having its own user and raffle types, it would use the types from the
domain package instead.
This would still follow hexagonal architecture, as the ‘core’ package – in this case the
domain package – wouldn’t be using anything from the
api package. It would only work the other way around.
However, Kat believes that although it’s likely fine for small apps, for larger applications it might be necessary to introduce marshalling between the layers of the hexagon, and the original example in her talk should have shown that.
For example, your
api layer’s representation of something might differ from the
domain representation of the same thing. You could have a
domain object use a string to represent something, and the
api layer could instead use bytes to represent the same thing.
├── main.go ├── api │ ├── client.go │ ├── raffle.go │ └── user.go ├── domain │ ├── raffle.go │ └── user.go ├── database │ └── client.go
That’s all folks!
Thank you, Kat, for your time and the chat!
To the readers, thank you for your attention. I’d love to hear your thoughts and how your opinions may have changed over the years.
If you want a more in-depth look into Go application structures from the voice of Kat herself, please check out her app-structure-examples repository.
Kat made this repository (almost immediately) after our interview to showcase some examples of the project structures she mentioned in her talk using a recent Advent of Code of challenge.
It includes additional commentary on each example and an FAQ section!
Teeny tiny final thing
I found another really cool repository while I was working on this article and wanted to share it with you. It was really interesting to explore, and I’d highly recommend giving it a look!
I found it funny how in the Readme, it states, “If you are trying to learn Go or if you are building a proof of concept or a simple project for yourself this project layout is an overkill. Start with something really simple instead (a single main.go file and go.mod is more than enough).”
This is essentially how my chat with Kat started. Perhaps this view is the new trend, or perhaps that’s how the trend has always been?
Anywho, have a cool day!
Subscribe to Blog updates
Does Machine Learning in Go Have a Future?
In this article, you'll explore the challenges of actively using Go for Machine Learning.
OS in Go? Why Not?
In this article, you'll learn why languages like C have a stronghold over OS development and whether writing an OS using Go is possible.
Comparing database/sql, GORM, sqlx, and sqlc
This article compares the database/sql package with 3 other Go packages, namely: sqlx, sqlc, and GORM. The comparison focuses on 3 key areas – features, ease of use, and performance.