Go package: a brief tour into the world of Go Module
Tags: go, tutorial, versioning
Published: 2020-04-26 20:37:03

Starting from Go 1.11, the Golang team decided to introduce a new dependency management system: the Modules.

A Module is a collection of Go Packages with explicit version information. The go.mod file is used to store that information and it is located at the root of the project tree. The idea behind this concept is similar to what npm is for Node.

In this post, we will see how the main project interacts with the go.mod file and how it handles the different package versions available. First of all, we need a package with some releases, let's develop it.

## go package

The idea behind the package is to build something extremely simple, whose only purpose is to print its version on the terminal. I followed the best practice rules related to the semantic versioning: `v<Major>.<Minor>.<Patch>`. I've already deployed the project on my personal GitHub account [github.com/enbis/versioningPack](https://github.com/enbis/versioningPack), but nothing stops you from making your own new one. Below the tree of the project folder.

```
~$ tree
.
├── go.mod
├── README.md
├── versioning.go
└── versioning_test.go
``` 

### the first release - v1.0.0

As a first release, it's fair to use the v1.0.0 as a version identification. So, let's write a few lines of code to achieve that. 

The versioning.go file:

``` go
package versioning

import "fmt"

func GetVersion() {
	fmt.Println("Version 1.0.0 pulled from remote repository")
}
```

The versioning_test.go:

``` go
package versioning

import "testing"

func TestVersioning(t *testing.T) {
	GetVersion()
}
```

The go.mod file:

``` go
module github.com/enbis/versioningPack

go 1.13
```

Well, everything seems to work as expected. The GetVersion function prints the version on the terminal and we can be satisfied with our job. It's time to push the changes and tag the first release with the v1.0.0 version.

Git versioning:

``` bash
$ git add .
$ git commit -m 'first release is ready'
$ git push origin master
$ git tag v1.0.0
$ git push --tag origin master
```

### the second release - v1.1.0

Assumption: minors changes required. So, let's start working on the new features (in that specific case I'm just talking about change the string printed out). As soon as the string is modified (and tested the result), we are ready to push changes and tag in order to create a new release: `v1.1.0`.

The versioning.go file:

```go 
package versioning

import "fmt"

func GetVersion() {
	fmt.Println("Version 1.1.0 pulled from remote repository")
}
```

Git versioning:

``` bash
$ git add .
$ git commit -m 'second release is ready'
$ git push origin master
$ git tag v1.1.0
$ git push --tag origin master
```

### the first major release - v2.0.0

Assumption: new changes required. This time the changes modify the package so much so that the new version won't be longer backward compatible. It's time to create a new feature branch, called v2, to prevent incompatibility problems. That's not enough since we are developing a package so the reference on the go.mod file must also be aligned with the branch.

The versioning.go file:

``` go
package versioning

import "fmt"

func GetVersion() {
	fmt.Println("Version 2.0.0 pulled from remote repository")
}
```

The go.mod file:

``` go
module github.com/enbis/versioningPack/v2

go 1.13
```

Git versioning (and branching):

``` bash
$ git checkout -b v2
$ git add .
$ git commit -m 'new release is ready'
$ git push origin v2
$ git tag v2.0.0
$ git push --tag origin master
```

### all the releases

The job with that package seems finished. Let's recap the versions available.

* two releases on branch master: v1.0.0 and v1.1.0
* one release on branc v2: v2.0.0

``` git
* 633ad5d (HEAD -> master, tag: v1.1.0, origin/master, origin/HEAD) version 1.1.0 ready
* 215b94a (tag: v1.0.0) rename
* 9e6c026 go mod master
| * 3aa6e2c (tag: v2.0.0, origin/v2, v2) rename
| * dc39bee go mod v2
| * 39f1fad v2.0.0
|/  
* 75ddd41 versioning
* 49d2c42 first commit
```

## the project

It's time to create a project in order to use the newly developed (and versioned) package. The purpose of that project is to test all the versions available of the github.com/enbis/versioningPack through the go.mod. As the last aspect, we will try to customize the behavior of the package locally and referring it instead of the release version. 

Firstly, we need to create the development environment, out of the $GOPATH. Select the path you prefer and run the commands below.

``` bash
~$ mkdir Projects/testVersioning
~$ cd Projects/testVersioning/
~/Projects/testVersioning$ touch main.go
~/Projects/testVersioning$ go mod init versPack
~/Projects/testVersioning$ ll
	totale 12
	drwxr-xr-x 2 enrico enrico 4096 apr 26 10:45 ./
	drwxr-xr-x 5 enrico enrico 4096 apr 26 10:42 ../
	-rw-r--r-- 1 enrico enrico   25 apr 26 10:45 go.mod
	-rw-r--r-- 1 enrico enrico    0 apr 26 10:43 main.go
~/Projects/testVersioning$ cat go.mod 
	module versPack

	go 1.13
```

### let's using the latest package's version available

Pretty easy use the latest version of the package: just pull it and the go.mod will search for the latest tag on the master branch.

``` bash
~/Projects/testVersioning$ go get github.com/enbis/versioningPack
	go: finding github.com v1.1.0
	go: finding github.com/enbis v1.1.0
~/Projects/other/gomod$ cat go.mod 
	module versPack

	go 1.13

	require github.com/enbis/versioningPack v1.1.0 // indirect
```

Let's try the package's feature in the main function.

``` go
package main

import (
	v1 "github.com/enbis/versioningPack"
)

func main() {
	v1.GetVersion()
}
```

Looking at the output generated confirms the go.mod is referring to the v1.1.0.

``` bash
~/Projects/testVersioning$ go run main.go 
	Version 1.1.0 pulled from remote repository
```

## let's using the package's version v1.0.0

Now, we'd like to work with the previous version of the versioningPack. We start deleting both go.mod and go.sum file, we need to init again the go.mod and pull a specific version of the github.com/enbis/versioningPack package. The secret lies in formatting the request when you pull the package, adding `@` you can request a specific version among the tags contained on the master branch.

``` bash
~/Projects/testVersioning$ rm go.mod go.sum
~/Projects/testVersioning$ go mod init versPack
~/Projects/testVersioning$ go get github.com/enbis/versioningPack@v1.0.0
	go: finding github.com v1.0.0
	go: finding github.com/enbis v1.0.0
~/Projects/other/gomod$ cat go.mod 
	module versPack

	go 1.13

	require github.com/enbis/versioningPack v1.0.0 // indirect
```

As expected we pulled the v1.0.0. That's will be confirmed by running the main function.

``` bash
~/Projects/testVersioning$ go run main.go 
	Version 1.0.0 pulled from remote repository
```

## let's using the package's version v2.0.0

What happened to our new major version? How can we pull it? If we try to get it using the request `go get github.com/enbis/versioningPack@v2.0.0` an error occurs: invalid version module. That because v2.0.0 resides on the v2 branch. So, we need to specify it when we launch the request. Let's try with `go get github.com/enbis/versioningPack/v2`, the suffix matches the branch name.

``` bash
~/Projects/testVersioning$ go get github.com/enbis/versioningPack/v2
~/Projects/other/gomod$ cat go.mod 
	module versPack

	go 1.13

	require github.com/enbis/versioningPack/v2 v2.0.0 // indirect
```

All we need to do is rename the package, with the proper reference contained inside the go.mod.

``` go
package main

import v2 "github.com/enbis/versioningPack/v2"

func main() {
	v2.GetVersion()
}
```

Trying it, the result is fairly obvious.

``` bash
~/Projects/testVersioning$ go run main.go 
	Version 2.0.0 pulled from remote repository
```

## let's using two different version at the same time

Now, what we are going to do is using both major version in the same file. This is a little used case history in a normal case but is useful to explain the power of the module in Go. You can make the two major versions coexist in the same solution, as long as they reside on two different branches and have different references in their go.mod respectively.

``` bash
~/Projects/testVersioning$ go get github.com/enbis/versioningPack/v2
~/Projects/other/gomod$ cat go.mod 
	module versPack

	go 1.13

	require (
		github.com/enbis/versioningPack v1.1.0 // indirect
		github.com/enbis/versioningPack/v2 v2.0.0 // indirect
	)
```

As usual, two aliases are required to refer to the two versions of the same package.

``` go
package main

import (
	v1 "github.com/enbis/versioningPack"
	v2 "github.com/enbis/versioningPack/v2"
)

func main() {
	v1.GetVersion()
	v2.GetVersion()
}
```

This is the output.

``` bash
~/Projects/testVersioning$ go run main.go 
	Version 1.1.0 pulled from remote repository
	Version 2.0.0 pulled from remote repository
```

## let's testing a new feature of the package before versioning it

So far, we saw how to use different versions of the same package and how to make two different versions coexist together. Great, but there is more. You can replace the path of the package inside the go.mod file in order to refer to your own version of the same package. Just add the keyword `replace` and the path of the package you want to replace.

``` bash
~/Projects/other/gomod$ cat go.mod 
	module versPack

	go 1.13

	require (
		github.com/enbis/versioningPack v1.1.0 // indirect
		github.com/enbis/versioningPack/v2 v2.0.0 // indirect
	)
	
	replace github.com/enbis/versioningPack => ../versioningPack
```

That's is pretty clear, I copied the package one folder above the location of the project folder. The last line under the "require" defines the original versioningPack is now replaced with the local package. Let's try to make some changes to the local package. 

``` go
package versioning

import "fmt"

func GetVersion() {
	fmt.Println("Version 1.1.1 from local folder")
}
```

And run the main function of the project.

```
~/Projects/testVersioning$ go run main.go 
	Version 1.1.1 from local folder
	Version 2.0.0 pulled from remote repository
```

## conclusion

That's just the tip of the iceberg regarding Go packages and module but I hope you found this analysis interesting, despite the simplicity of the solution. Enjoy your time developing your new Go packages, which I'm sure will be much more useful than mine github.com/enbis/versioningPack.