Go logo

GoLand

A cross-platform Go IDE with extended support for JavaScript, TypeScript, and databases

Tutorials

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.

portrait picture of kat zien

Kat Zien works at Monzo as a senior backend engineer and tech lead on the Operations team. Her main interests include automating #allthethings, sorting out legacy code and making things simpler and faster. She was co-organising PHP South West and London Gophers. Kat loves travelling and keeping active, appreciates good coffee and is a big Lego fan. She cycles across Europe with TechBikers to raise money for Room To Read.

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

Multi-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 folder

├── 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 pkg folder.

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.

Essentially, the 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?

cmd removed

├── 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

An internal directory showcases to anyone looking at your service that this code is private. External services should not import any packages within this directory.

The 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!

https://github.com/golang-standards/project-layout

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!

image description