🚀 Get 25% off access to all my premium courses - use discount code FUNCMAIN at checkout - view the pricing page now!

Video:

Packages in Go

January 1, 0001

Course Instructor: Elliot Forbes

Hey Gophers! My name is Elliot and I'm the creator of TutorialEdge and I've been working with Go systems for roughly 5 years now.

Up until this point, we’ve done all of our Go programming within a package main. This has been purely for educational purposes and also due to the fact that each of the topics we covered didn’t warrant splitting out anything into multiple packages.

Now we’ve entered the major leagues though, and we need to start looking at how we can deconstruct our application into a series of modular packages that are easy to test, self-contained and loosely coupled.

What Are Packages?

Before we dive into the code, let’s first consider what packages are first.

When I think of packages, I like to think of self-contained logical groups of code. For instance, in this tutorial we are going to be implementing some network based commands that will perform network actions such as pinging an IP address or scanning a given port.

Depending on the size/complexity of each of these tasks, I would tend to group them all under a network package which would contain only the code needed to perform any network specific actions.

This is a key point that I should highlight. When crafting your software, try and ensure that you write it in such a way that each function is only responsible for one given task. For example, when we implement the ability to Ping an IP address, the function that we’ll create should only send a network ping to this IP address and return the response.

Good examples of this exist within the Go standard library itself, so it’s worthwhile taking a quick look at the packages there to see this in action. One good example is the encoding/json package.

Creating our First Package

Let’s start off by creating a new directory called internal. This will house all of the internal implementation details for our application such as the code to ping IPs or to scan ports.

Within the internal/ directory, let’s create a new directory called network. Note that I’m keeping this fairly high-level and abstract for the purposes of this course, but you may want to create more granular packages such as port or ping depending on your own needs.

Within this network directory, let’s create a new file called ping.go.

internal/network/ping.go
package network

import "fmt"

// Ping - takes in an IP and pings that IP and returns
// the response.
func Ping(ip string) {
    fmt.Println(ip)
} 

Important Note - We’ve capitalized the ‘P’ in ‘Ping’ which denotes that this is an exported, or public function which can be called from other packages. Leaving it lowercase would mean it was a private function which isn’t directly accessible from other packages.

Updating our Entrypoint

With this package now defined, let’s take a look at how we can import this package into our entrypoint and call the exported Ping function. Now, in the past we’ve used standard library packages such as fmt and net, however, as this is a package within our project we need to be more explicit in how we import this.

In order to successfully import our package, we need to provide the full path to the package that we’ve created. We’ll have to provide the repository name followed by the sub-directories to our new package. In this case, I have a repo called github.com/TutorialEdge/network-cli and the package can be found in internal/network, this gives us an import path that looks like github.com/TutorialEdge/network-cli/internal/network:

cmd/cli/main.go
package main

import (
  "fmt"

  "github.com/TutorialEdge/network-cli/internal/network"
)

func main() {
  fmt.Println("Network CLI")
  network.Ping("128.0.0.1")
}

Notice how we’ve called the Ping function. We’ve prefixed the call to the function with the name of the package, in this case it’s network.

With these changes now in place, let’s attempt to run this locally:

$ go run cmd/cli/main.go
Network CLI
128.0.0.1

Perfect, we’ve been able to import our network package and then call the public Ping function that we’ve defined within that package!

Sub-packages

As it stands, our network package is fairly lightweight and we’ve only got one file which has contains the functionality to Ping an IP address. As our go applications become more advanced though, we may need to further split our packages to make them more maintainable and stop them growing into monolithic monsters that plague us with nightmares everytime we need to update them.

Hypothetically, if we wanted to create a sub-package for ping, we could create a new sub-directory within our network directory and then change the package declaration at the top to package ping. With these changes in place, we would then have to update everywhere in our code that we used the original network package to use this new ping package.

Conclusion

Perfect! So in this video, we have looked at how we can define our own packages within our application and then how we can handle importing and calling the exported functions from this package in other sections of our code.