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
.
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
:
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:
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.