Golang Packaging Structure & Best Practices [Tutorial]

We are going to learn how to arrange your Go code into reusable packages, import packages, export functions, types, and variables to other packages, and install third-party packages in this tutorial.

When working with a programming language, due to the limitation of available libraries, we are bound to write our own library or at least use someone else's library. The DRY principle, which essentially states that you should never write the same code again, is one of the guiding principles of high-quality software. As far as possible, you should reuse and expand upon existing code.

Advertisement

Because everyone has a different writing style, it makes it very difficult for everyone to use each other's libraries, and even within an individual version there are changes that make for the use of others is affected. To solve this problem, the community has a common standard for a library that is written in compliance with that community's library manager, for example, if you write Nodejs Package, you must adhere to npm rules, or if you write a library in golang then you must conform to the go module standards.

The most fundamental building blocks that enable code reuse are functions. The subsequent development in code reuse is the packaging. They assist in making Go source files modular, reusable, and maintainable by grouping comparable files into a single unit.

In GoLang, there are two concepts related to package management: Go Package and Go Module. Each Go Package is a collection of files in the same directory, aiming to handle a set of problems related to what that Package aims to do. Go Module is a collection of Go Packages, each project is a module, and the packages used in the module are managed by GO with the go.mod file.

 

Introduction to Go Module

Go 1.11 and 1.12 include preliminary support for modules, Go’s new dependency management system that makes dependency version information explicit and easier to manage. This blog post is an introduction to the basic operations needed to get started using modules.

A module is a collection of Go packages stored in a file tree with a go.mod file at its root. The go.mod file defines the module’s module path, which is also the import path used for the root directory, and its dependency requirements, which are the other modules needed for a successful build. Each dependency requirement is written as a module path and a specific semantic version.

To make a directory the root of a module by using go mod init:

Advertisement
go mod init example.com/hello

 

Introduction Go Package

A package is only a directory inside your Go workspace that contains one or more Go source files or other Go packages. Every Go source file must belong to a package. The following syntax is used to declare a source file as a component of a package:

package <packagename>

The first line of code in your Go source file must be the package declaration shown above. Your Go source file's defined variables, types, and functions all become a part of the declared package. The structs or functions that are exported from your package can be imported and used by other packages.

The following package appears in almost all of the code so far in our Golang articles:

import "fmt"

fmt is a built-in package that implements formatted I/O with functions analogous to C's printf and scanf. The format 'verbs' are derived from C's but are simpler.

 

The benefits of package management

  • Reducing conflicts: The names of functions might be the same across many packages. This maintains the names of our functions short and brief.
  • Making your program organized: It groups together pieces of code that are related to make it simpler to locate the code you want to reuse.
  • Boosting up your performance: just the smaller, modified portions of the program need to be recompiled. We don't have to recompile the fmt package every time change our program.

 

Importing Packages

There are two ways to import (external or internal) packages in Go:

// Multiple import statements
import "fmt"
import "time"
import "math"
import "os"

Or:

import (
	"fmt"
	"time"
	"math"
        "math/rand"
)

The package name is the same as the import path's final component. As an illustration, rand is the name of the package imported as math/rand. Because it is nested inside the math package as a subfolder, it is imported with the path math/rand.

 

Package name convention; export structs, and functions in a package

By convention, packages are given lowercase, single-word names; there should be no need for underscores or mixedCaps. Err on the side of brevity, since everyone using your package will be typing that name. The package name is only the default name for imports; it need not be unique across all source codes, and in the rare case of a collision the importing package can choose a different name to use locally. In any case, confusion is rare because the import file name determines which package is being used.

Anything (variable, type, or function) that starts with a capital letter will be exported and can be used outside of the package while things that do not start with a capital letter will be not exported, and is visible only inside the same package.

When importing a package, you can only use its exported names (the ):

package main

// Importing packages
import (
	"fmt"
	"math"
)

func main() {
	// Exported Min function
	fmt.Println(math.Min(10, 91))

	// Exported Sqrt function
	fmt.Println(math.Sqrt(144))

	// the value of `𝜋`
	fmt.Println(math.Pi)
}

Output:

10
12
3.141592653589793

If we change to fmt.Println(math.pi) the output will be: .\packages.go:17:19: undefined: math.pi because pi is not an exported constant.

Advertisement

 

Creating and managing custom Packages

Here is an example of creating a sample Go project that has multiple custom packages and how to use the concept of package declaration, imports, and exports apply to custom packages.

Let's begin by entering the following commands to initialize a Go module:

mkdir helper
cd helper
go mod init helper

Now let's build some source files and add them to various project packages. All of the packages and source files are shown in the following image:

Golang Packaging Structure & Best Practices [Tutorial]

Here is the code inside every source file:

string/upperCase.go

package string

import "strings"

// return an upper case string
func Upper(s string) string {
	return strings.ToUpper(s)
}

slice/sizeSlice.go

Advertisement
package slices

//return slice size
func sliceSize(a []interface{}) int {
	return len(a)
}

slice/intSlice/sortInt.go (Nested package)

package intSlice

import "sort"

func SortInt(a []int) []int {
	sort.Ints(a)
	return a
}

main.go (The main package: entry point of our program)

package main

import (
	"fmt"

	"helper/slice/intSlice"
	str "helper/string"
)

func main() {
	fmt.Println(intSlice.SortInt([]int{14, 5, 19, 10}))

	fmt.Println(str.Upper("golinucloud"))
}

Now we are going to build our module to executable binary:

go build

Let’s execute the binary file to run the program on Windows:

helper.exe  
[5 10 14 19]
GOLINUCLOUD

There are some things that should be noted here:

  • Import Path: you have to import the relative path to the module
  • Package Alias: Package alias can be used to provide the imported package a brief name or to address disputes between different packages with the same name.
  • Nested package: We can nest a package inside another. It’s as simple as creating a subdirectory.
  • Unexported functions:  We can not use slice/sizeSlice.go in main.go because this function is not exported.

 

Install additional packages

With Go modules, including third-party packages in your project is quite simple. Simply import the package into any source files in your project, and Go will download it for you the next time you build or run the project.

Advertisement

Here is an example of import gopkg.in/yaml.v2: the yaml package enables Go programs to comfortably encode and decode YAML values

package main

import (
	"fmt"
	"log"

	"gopkg.in/yaml.v2"
)

var data = `
a: Easy!
b:
  c: 2
  d: [3, 4]
`

// Note: struct fields must be public in order for unmarshal to
// correctly populate the data.
type T struct {
	A string
	B struct {
		RenamedC int   `yaml:"c"`
		D        []int `yaml:",flow"`
	}
}

func main() {
	t := T{}

	err := yaml.Unmarshal([]byte(data), &t)
	if err != nil {
		log.Fatalf("error: %v", err)
	}
	fmt.Printf("--- t:\n%v\n\n", t)

	d, err := yaml.Marshal(&t)
	if err != nil {
		log.Fatalf("error: %v", err)
	}
	fmt.Printf("--- t dump:\n%s\n\n", string(d))

	m := make(map[interface{}]interface{})

	err = yaml.Unmarshal([]byte(data), &m)
	if err != nil {
		log.Fatalf("error: %v", err)
	}
	fmt.Printf("--- m:\n%v\n\n", m)

	d, err = yaml.Marshal(&m)
	if err != nil {
		log.Fatalf("error: %v", err)
	}
	fmt.Printf("--- m dump:\n%s\n\n", string(d))
}

Output:

go run main.go
go: downloading gopkg.in/yaml.v2 v2.4.0
go: downloading gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
go: downloading github.com/kr/pretty v0.3.1
go: downloading github.com/kr/pty v1.1.8
go: downloading github.com/kr/text v0.2.0
go: downloading github.com/creack/pty v1.1.18
go: downloading github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
go: downloading github.com/rogpeppe/go-internal v1.9.0
go: added github.com/creack/pty v1.1.18
go: added github.com/kr/pretty v0.3.1
go: added github.com/kr/pty v1.1.8
go: added github.com/kr/text v0.2.0
go: added github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
go: added github.com/rogpeppe/go-internal v1.9.0
go: added gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
go: added gopkg.in/yaml.v2 v2.4.0

 

Manually installing packages

We can use go get command to download 3rd party packages from remote repositories:

go get [-d] [-f] [-t] [-u] [-v] [-fix] [-insecure] [build flags] [packages]

get downloads the packages named by the import paths, along with their dependencies. It then installs the named packages, like 'go install'.

The -d flag instructs get to stop after downloading the packages; that is, it instructs get not to install the packages.

The -f flag, valid only when -u is set, forces get -u not to verify that each package has been checked out from the source control repository implied by its import path. This can be useful if the source is a local fork of the original.

Advertisement

Example:

go get github.com/gorilla/websocket

And now you can import and use this package:

import "github.com/gorilla/websocket"

 

Summary

This article taught you how to create custom packages, install third-party packages, import packages, export package members, and organize Go code into reusable packages.

 

References

https://go.dev/blog/using-go-modules
How to Write Go Code - The Go Programming Language
https://go.dev/doc/effective_go#package-names

 

Categories GO

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment

X