Packages

Overview

Packages are how Go organizes code. It is also how scope and visibility are determined. This chapter will cover both executable and library packages. You will also learn how to create your own package and use it within another package.

What Are Packages

Go code is organized in packages. A package represents all the files in a single directory on disk. One directory can contain only files from the same package.

You’ve seen this already several times. Our examples so far have all used package “main” declared at the top of the file.

Package Names

While a package can only have one name, it is not required that the name be the same as the folder it is in. However, it is strongly encouraged to match the folder.

For example, code in folder “foobar” should declare package foobar.

Source files in a package must declare the package name at the top of the file as the first code statement.

package foobar

// code goes here

All source files (.go) must declare the package name at the top of the file. No exceptions!

Executable Packages

Executable programs must have a main package that declares a main() function:

package main

func main() {
	// do work here
}

The main function can only be declared once and receives no arguments, nor does it return any values.

Package Resolution

With Go modules, your code can live anywhere on disk. Package resolution is based on the module path declared in your go.mod file.

For example, if your go.mod declares:

module github.com/mycompany/myproject

Then a package in the red subdirectory would be imported as:

import "github.com/mycompany/myproject/red"

The package name should match the folder name. Code in folder red should declare package red.

Packages from external repositories use their full repository path as the import path. For example, the source code at https://github.com/gobuffalo/buffalo would be imported using the following import path:

import "github.com/gobuffalo/buffalo"

Go automatically downloads and caches dependencies in the module cache (typically $GOPATH/pkg/mod).

Scope And Visibility

Go does not have the concept of public, private, or protected modifiers like other languages do.

External visibility is controlled by capitalization. Types, Variables, Functions, etc… that start with a capital letter are available, publicly, outside the current package.

A symbol that is visible outside its package is “exported”.

Types, Variables, Functions, etc… that start with a lower case letter are unexported and are not available outside of the current package.

All variables and types declared inside a package are visible to everything else in the same package.

// visible outside of the package:
func Foo() {}

// available only inside the package:
var bar string

Security

It is important to understand that although you can’t directly access or change unexported fields in a struct, you can still get access to view the contents of them.

This is because the fmt package makes use of the reflect package.


package main

import (
	"fmt"

	"github.com/gopherguides/foo"
)

func main() {
	user := foo.NewUser("Cory", "LaNou", "p@ss0rd")
	// You can see the contents of the private information...
	fmt.Printf("%+v\n", user)
	// output:  {First:Cory Last:LaNou password:p@ss0rd}

	// You can't access or change it directly....

	//fmt.Println(user.password)
	//user.password = "new"

	// output:
	// ./foo_example.go:17:18: user.password undefined (cannot refer to unexported field or method password)
	// ./foo_example.go:18:6: user.password undefined (cannot refer to unexported field or method password)
}

Bad Practice

While it is possible to return a non-exported type from an exported function, it is considered bad practice.

// available only inside the package:
type bar struct {}

// "legal", but not encouraged
func Foo() bar {
  return bar{}
}

Consider return a publicly exported struct or interface instead.

File Names

Inside packages there are no requirements as to what the names of the files inside that package are to be named.

However, it is common practice to name the “entry point” file after the name of the package.

For example, a package named store would probably have a store.go file inside as the main entry point to that package. It is also the file where you would write your top level comments that will show up when you generate your code documentation.

Package Organization

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

Inevitably, every developer in Go asks the following question:

How do I organize my code?

There are a number of articles and approaches, and while some work well for some, they may not work well for others.

No Approach Is Perfect

It’s important to understand that no approach is perfect, and this approach won’t work for every application. If you are spending time trying to figure out where to put your packages, this may be a good option, if for nothing else, to prevent you from losing time/momentum on your project.

The approach that is discussed in this module is based on Ben Johnson’s article on Standard Package Layout.

Write The Code

The most important piece of advice we can give you on application layout is to write some code, get it working, and then refactor. Many times you won’t see a pattern or relation until your code starts to take shape. It’s very common on larger projects to split packages apart, combine other packages, and move functionality from one package to another.

Understanding Circular Dependencies

A circular dependency occurs when one package imports another package that directly or indirectly imports the first package.

In our example, pkg1 imports pkg2. pkg2 doesn’t import pkg1 directly, however, it imports pkg3 which in turn imports pkg1.

myproject/
└── pkg1 // imports pkg2
└── pkg2 // imports pkg3
└── pkg3 // imports pkg1
package pkg1

import "myproject/pkg2"
package pkg2

import "myproject/pkg3"
package pkg3

import "myproject/pkg1"

Anti-Pattern: Group By Module

One approach is to group code into packages by module. In our example below, we group user-related functionality and order-related functionality into separate packages.

This approach breaks down when the users package needs to reference the orders package and the orders package needs to reference the users package.

myproject/
└── users
│   ├── user.go
└── orders
    └── order.go

Recommended Approach

One of the most common approaches to package layout is to separate out your application into 4 package types:

  1. Domain Package
  2. Implementation Packages
  3. Mock Package
  4. Binary Packages

This is similar to the Clean Architecture advocated by Robert Martin (aka “Uncle Bob”).

Domain Package

The domain package contains your application-specific types and it lives at the root of your project.

This package includes concrete data types and it includes interfaces for services that your application relies on. It can also include functionality that only relies on these data types and services.

  • Data Types: Account, User structs
  • Services: AccountService, UserService interfaces
  • Other: SortUsers() function or a UserCache struct

Implementation Packages

Implementations of your domain service interfaces are grouped into packages. The package name is typically the technology used in the implementation.

For example, a Postgres implementation of our UserService interface would live in a postgres subpackage. If you use Redis as a cache in front of your Postgres server then a separate UserService implementation will live in the redis subpackage.

Each implementation should isolate the technology inside of it. The postgres package should be the only place direct Postgres calls are made in your application. These implementation packages should not import each other either.

This separation of implementations makes it easy to inject mocks at each layer so you can unit test each layer separately.

myproject/
└── user.go      // Domain service interface
└── postgres
│   ├── user.go  // Postgres service implementation
└── redis
    └── user.go  // Redis service implementation

Mock Package

A single mock package allows all your packages to share mock implementations of your domain interfaces. By using domain interfaces we can test each layer of our application in isolation.

myproject/
└── mock
    └── user.go  // Mock service implementation

Binary Packages

Finally, we will create subpackages for each of our output binaries under the cmd package in our project. For example, you may have a myprojectd server binary and a myprojectctl client binary.

myproject/
└── cmd
    ├── myprojectd
    │   └── main.go  // Server binary.
    └── myprojectctl
        └── main.go  // Client binary.

Overall Layout

Our final project layout for an application using postgres and redis should look like this:

myproject/
├── cmd
│   ├── myproject
│   │   └── main.go  // Server binary.
│   └── myprojectctl
│       └── main.go  // Client binary.
├── mock
│   └── user.go      // Mock user service implementation.
├── postgres
│   └── user.go      // Postgres user service implementation.
├── redis
│   └── user.go      // Redis user service cache implementation.
└── user.go          // User domain types and service interfaces.

Large Repositories

This package layout approach works well for small to medium sized applications. For large applications or monorepos, you need to divide your domain into subdomains.

Your root domain will still contain domain types and interfaces that are shared across all your subdomains. However, each subdomain should be isolated and not reference other subdomain packages.

Package Layout Benefits

This approach to package layout gives you several benefits:

  1. Decouples application layers by communicating via well defined interfaces.
  2. Allows each package to be unit tested in isolation.
  3. Avoids circular dependencies.
  4. Allows application to cleanly grow over time.

Internal Packages

Go provides a special internal directory convention for packages that should only be imported by nearby code.

Any package within an internal directory can only be imported by packages rooted at the parent of the internal directory.

Example Structure

myproject/
├── cmd/
│   └── myapp/
│       └── main.go      # Can import internal/auth
├── internal/
│   └── auth/
│       └── auth.go      # Only importable within myproject
└── go.mod

In this example, internal/auth can be imported by cmd/myapp and any other package within myproject, but cannot be imported by external projects.

When To Use Internal

Use internal packages for:

  • Implementation details that shouldn’t be part of your public API
  • Shared code between multiple commands in the same module
  • Code that isn’t stable enough to commit to as a public interface
// This import works from within myproject
import "github.com/mycompany/myproject/internal/auth"

// This import would FAIL from an external project
// import "github.com/mycompany/myproject/internal/auth"

The Go compiler enforces this restriction, providing a strong guarantee about package visibility.

Spot The Bug


package main

import "fmt"

func main() {
	fmt.Println("Hello, World")

	fmt := `my format string: %s`

	fmt.Printf(fmt, "some more text")
}

Bug Explained

In this example the package name fmt was redeclared as a string type in the main function.


package main

import "fmt"

func main() {
	fmt.Println("Hello, World")

	// Redeclaring the `fmt` namespace here
	fmt := `my format string: %s`

	// `fmt` is now a string variable, not a package namespace
	fmt.Printf(fmt, "some more text")
}

Since this is legal to do in Go, you must becareful that don’t accidently override package names in this manner.

Troubleshooting

When working with Go modules, there are several common issues you may encounter.

Missing go.mod File

If you see an error like go: go.mod file not found, you need to initialize your module:

$ go mod init github.com/yourname/yourproject

Dependency Issues

Use go mod tidy to clean up and verify your dependencies:

$ go mod tidy

This command adds missing dependencies and removes unused ones from your go.mod file.

Understanding Dependencies

To see why a particular module is required:

$ go mod why github.com/some/dependency

To visualize the entire dependency graph:

$ go mod graph

Module Cache

Downloaded modules are cached in $GOPATH/pkg/mod. To clear the cache:

$ go clean -modcache

Verifying Dependencies

To verify that dependencies haven’t been modified:

$ go mod verify

Managing Dependencies

Go modules provide built-in dependency management. Here’s how to work with external packages.

Adding Dependencies

To add a dependency to your project, use go get within a module:

$ go get github.com/gobuffalo/flect

This adds the package to your go.mod file and downloads it to the module cache.

You can also specify a version:

$ go get github.com/gobuffalo/flect@v0.3.0

Updating Dependencies

To update a dependency to its latest version:

$ go get -u github.com/gobuffalo/flect

To update all dependencies:

$ go get -u ./...

Installing Command-Line Tools

To install executable tools (like linters, code generators, etc.), use go install with a version:

$ go install golang.org/x/tools/cmd/goimports@latest

This installs the binary to $GOPATH/bin (or $GOBIN if set). Make sure this directory is in your PATH.

Note: Use go install pkg@version for tools. The go get command is for managing dependencies in your go.mod file, not for installing executables.

Initializing A Module

To start a new Go module project:

$ go mod init github.com/yourname/yourproject

This creates a go.mod file that tracks your module path and dependencies.

Exercise

Create a new Go module with a package foo containing a User struct with the following fields:

First - string, exported
Last - string, exported
password - string, not exported
  1. Create a directory for your project (can be anywhere on disk)
  2. Initialize a Go module with go mod init github.com/gopherguides/foo
  3. Create a foo.go file with your User struct
  4. Create a cmd subdirectory with a main.go file

From the main package, create a new value of type User and then print it out.

Hint:

To access your User from the main package you will need to first import the github.com/gopherguides/foo package and then you can reference foo.User in your code.

Your directory structure should look like this when finished:

foo/
├── cmd/
│   ├── go.mod
│   └── main.go
├── foo.go
└── go.mod

Note: When developing locally, you’ll use a replace directive in cmd/go.mod to reference the local foo package.

Solution


package foo

type User struct {
	First    string
	Last     string
	password string
}

// NewUser returns a user with the intialized values provided
func NewUser(first, last, password string) User {
	return User{
		First:    first,
		Last:     last,
		password: password,
	}
}
$ go mod init github.com/gopherguides/foo

module github.com/gopherguides/foo

go 1.21

package main

import (
	"fmt"

	"github.com/gopherguides/foo"
)

func main() {
	u := foo.User{
		First: "Rob",
		Last:  "Pike",
	}

	fmt.Println(u)
}
$ go mod init github.com/gopherguides/foo/cmd

Edit go.mod file and place the following line at the bottom:

replace github.com/gopherguides/foo => ../

This tells the go compiler to look for github.com/gopherguides/foo in the relative directory of ../.


module github.com/gopherguides/foo/cmd

go 1.21

require github.com/gopherguides/foo v0.0.0

replace github.com/gopherguides/foo => ../