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)
}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:
- Domain Package
- Implementation Packages
- Mock Package
- 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,Userstructs - Services:
AccountService,UserServiceinterfaces - Other:
SortUsers()function or aUserCachestruct
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:
- Decouples application layers by communicating via well defined interfaces.
- Allows each package to be unit tested in isolation.
- Avoids circular dependencies.
- 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.
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")
}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
- Create a directory for your project (can be anywhere on disk)
- Initialize a Go module with
go mod init github.com/gopherguides/foo - Create a
foo.gofile with yourUserstruct - Create a
cmdsubdirectory with amain.gofile
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,
}
}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.21module 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)
}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 => ../module github.com/gopherguides/foo/cmd
go 1.21
require github.com/gopherguides/foo v0.0.0
replace github.com/gopherguides/foo => ../