Inevitably, every Go developer asks the following question:

How do I organize my code?

There are many articles and approaches, and while some work well for some, they may not work well for others. Go has no official conventions and preferences on how you should structure your packages and where your non-code resources should reside.

Unlike other languages though, Go does not allow circular package imports. Projects thus require additional planning when grouping code into packages to ensure that dependencies do not import each other.

The goal of this article is not to specify a strict convention, but rather advocate building robust mental models for reasoning about your problem domains and how to represent them in your project layouts.

Project Layout Types

No approach is perfect, but there are a few that have gained widespread adoption. Please check the following resources:

Now we are going to explore what options are available for structuring your applications and distinguish between some good and some bad practices.

Flat Structure

Recommendations

When using a flat structure you should still try to adhere to coding best practices. Here are some helpful tips:

restaurant-app/  
  customer.go  
  data.go  
  handlers.go  
  main.go  
  reservation.go  
  server.go  
  storage.go  
  storage_json.go   
  storage_mem.go

package main

import (
	"net/http"
	"some"
	"someapi"
)

type Server struct {
	apiClient *someapi.Client
	router    *some.Router
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.router.ServeHTTP(w, r)
}

restaurant-app/
  cmd/
    web/
      # package main
      main.go
    cli/
      # package main
      main.go
  # package restaurantapp
  server.go
  customer_handler.go
  reservation_handler.go
  customer_store.go

Anti Pattern: Group by Function (Layered Architecture)

Layered architecture patterns are n-tiered patterns where the components are organized in layers.

This is the traditional method for designing most software and is meant to be self-independent, i.e. all the components are interconnected, but do NOT depend on each other.

We have all heard about the famous 3-tier MVC (Model-View-Controller) architecture where we split our application into the following 3 distinct layers:

Presentation / User Interface (View)

— Business Logic (Controller)

— Storage / External Dependencies (Model)

This architecture translated into our example project layout would look like this:

restaurant-app/
  # package main
  data.go
  handlers/
    # package handlers
    customers.go
    reservations.go
  # package main
  main.go
  models/
    # package models
    customer.go
    reservation.go
    storage.go
  storage/
    # package storage
    json.go
    memory.go
  ...

Anti Pattern: Group by Module

Grouping by Module offers us a slight improvement over the layered approach:

restaurant-app/
  customers/
    # package customers
    customer.go
    handler.go
  # package main
  main.go
  reservations/
    # package reservations
    reservation.go
    handler.go
  storage/
    # package storage
    data.go
    json.go
    memory.go
    storage.go
  ...

Group by Context (Domain Driven Design)

This way of thinking about your applications is called Domain Driven Design (DDD).

In its essence it guides you to think about the domain you are dealing with and all the business logic without even writing a single line of code.

Three main components need to be defined:

— Bounded contexts

— Models within each context

— Ubiquitous language

Bounded Contexts

A bounded context is a fancy term defining limits upon your models. An example would be, e.g., a User entity that might have different properties attached to it based on the context:

The bounded context also helps in deciding what has to stay consistent within a particular boundary and what can change independently.

Ubiquitous Language

Ubiquitous Language is the term used in Domain Driven Design for the practice of building up a common, rigorous language between developers and users.

This language is based on the Domain Model used in the software, and it evolves up to the point of being able to express complex ideas by combining simple elements of the Domain Model.

Categorizing the building blocks

Based on the DDD methodology we will now start to reason about our domain by constructing its building blocks.

If we take our Restaurant Reservation System example, we would have the following elements:

Now after defining those blocks we can translate them into our project layout:

restaurant-app/
  adding/
    endpoint.go
    service.go
  customers/
    customer.go
    sample_customers.go
  listing/
    endpoint.go
    service.go
  main.go
  reservations/
    reservation.go
    sample_reservations.go
  storage/
    json.go
    memory.go
    type.go

The main advantage here is that our packages now communicate what they PROVIDE and not what they CONTAIN.

This makes it easier to avoid circular dependencies, because:

Group by Context (Domain Driven Design + Hexagonal Architecture)

So far we managed to structure our application according to DDD, eliminated circular dependencies and made it intuitive what each package does only by looking at the directory and file names.

We still have some problems though:

Hexagonal Architecture

This type of architecture distinguishes the parts of the system which form your core domain and all the external dependencies are just implementation details.

Fig. 1: Hexagonal Architecture Layers Diagram

External dependencies could be databases, external APIs, mail clients, cloud services etc., anything that your application interacts with.

The problem this solves is giving you the ability to change one part of the application without affecting the rest, e.g., swapping databases or transport protocols (HTTP to gRPC).

This is not in any way similar to the MVC (layered) model, because:

Fig. 2: Layered Architecture Dependency Direction

Fig. 3: Dependency Inversion Direction

Recommendations

Based on this approach our project structure could look like this:

restaurant-app/
  cmd/
    # HTTP server
    restaurant-server/
      main.go
    # CLI app
    restaurant-cli/
      main.go
    # HTTP server with seeded data
    restaurant-sample-data/
      main.go
      sample_reservation.go
      sample_customers.go
  pkg/
    adding/
      reservation.go
      endpoint.go
      service.go
    listing/
      customer.go
      reservation.go
      endpoint.go
      service.go
    transport/
      http/
        server.go
    main.go
    storage/
      json/
        customer.go
        repository.go
        reservation.go
      memory/
        customer.go
        repository.go
        reservation.go

To solve the multiple app version binaries problem we utilize the cmd sub-directory pattern which we mentioned as an improvement to the flat structure layout.

We are now able to produce 3 different binaries used to serve different purposes:

We introduce the pkg package which separates our Go code from the cmd binaries and non-code resources, e.g. DB scripts, configs, documentation, etc. which should be found on the same level under the project's root directory.

NOTE

Using the cmd and pkg directories has become somewhat of a trend in the Go community. It is not a standard by any means, but a good recommendation that should be considered.

Conclusion

Unfortunately, there is no single right answer, but at least we outlined some examples of problem domains, how to reason about them and how to translate that reasoning into Go package organization. Great freedom comes with great responsibility! Use the following guidelines wisely:

Also published here.