Do you like my articles? I am happy to present them (or something similar) at your conference!
Drop me a DM, and let's chat! :)

In the past years, Kubernetes emerged as the go-to container scheduling and management platform. As I’d like to understand better what’s happening under the hood, I decided that I’ll learn the Go language.

In this article, I’ll summarize my learnings from a Node.js developer’s point of view. I’ll pay particular attention to:

  • dependency management,
  • how asynchronous operations are handled.

Let’s get to it :)

What’s Go?

Go is an open source programming language that makes it easy to build simple, reliable, and efficient software. - golang.org

Go was created in 2009 at Google by Robert Griesemer, Rob Pike, and Ken Thompson. It is a compiled language, which comes with static types. It has a garbage collector and handles asynchronous operations in a CSP style. Go has a C-like syntax:

1
2
3
4
5
package main
import "fmt"
func main() {
fmt.Println("hello world")
}

Installing Go

To install Go, follow the official setup guide: https://golang.org/doc/install.

Dependency management in Go

As someone who writes a considerable amount of JavaScript code, one of the first questions I had was how does Go handle dependency management? It seems two solutions have emerged:

go get is shipped with the Go language itself, while dep is a dependency manager for Go. In npm terms, you can think of them like this: use go get whenever you’d use npm install -g, and use dep for project specific dependencies.

To get dep, you could use go get to install it, using:

1
go get -u github.com/golang/dep/cmd/dep

However, there’s a drawback to using go get - it does not handle versions, it just grabs the head of the provided GitHub repository. This is why it is recommended that you install dep using these commands:

1
2
brew install dep
brew upgrade dep

(you can read instructions for different OSs here: Go install guide)

Once you have dep installed, you can create projects using dep init, just like you would do it with npm init.

Before start developing with Go, take some time to set up the GOPATH environment variable correctly - you can use the official Go install guide as a reference.

dep will create similar files that npm would - Gopkg.toml to describe the project, just like package.json in the Node.js ecosystem. The analogy for package-lock.json is Gopkg.lock, while instead of using the node_modules folder, dep puts dependencies into the vendor folder.

To add dependencies, you only have to run dep ensure -add github.com/pkg/errors. After running it, it will appear both in your lock and your toml file:

1
2
3
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"

Handling asynchronous operations in Go

When writing asynchronous JavaScript, we are used to some libraries/language features, like:

  • the async library,
  • Promises,
  • or async functions.

Using them, we can easily read files from the file system:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const fs = require('fs')
const async = require('async')
const filesToRead = [
'file1',
'file2'
]
async.map(filesToRead, (filePath, callback) => {
fs.readFile(filePath, 'utf-8', callback)
}, (err, results) => {
if (err) {
return console.log(err)
}
console.log(results)
})

Let’s see what it looks like in Go!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import (
"fmt"
"io/ioutil"
)
func main() {
datFile1, errFile1 := ioutil.ReadFile("file1")
if errFile1 != nil {
panic(errFile1)
}
datFile2, errFile2 := ioutil.ReadFile("file2")
if errFile2 != nil {
panic(errFile2)
}
fmt.Print(string(datFile1), string(datFile2))
}

Let’s see what happened in the code snippet above line by line:

  • import - with import, you can require packages your applications are built upon, just like require in Node.js,
  • func main - the entry point of our application,
  • ioutil.ReadFile - tries to read the file, and returns two values:
    • datFile1 if the operations were successful,
    • errFile1 if there was some error,
      • this is your chance to handle the error, or crash the process,
  • fmt.Print prints merely the results to the standard output.

The example above works, but reads the files one after each other - time to go asynchronous!

For handling threads, Go has a concept called goroutines. A goroutine is a lightweight thread managed by the Go runtime - it enables you to run Go functions concurrently.

To manage/synchronize goroutines, I ended up using the errgroup package. This package provides synchronization, error propagation, and Context cancellation for groups of goroutines working on subtasks of a common task.

Using errgroup, we can rewrite the file reading code snippet to run concurrently this way:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main
import (
"context"
"fmt"
"io/ioutil"
"os"
"golang.org/x/sync/errgroup"
)
func readFiles(ctx context.Context, files []string) ([]string, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([]string, len(files))
for i, file := range files {
i, file := i, file
g.Go(func() error {
data, err := ioutil.ReadFile(file)
if err == nil {
results[i] = string(data)
}
return err
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
func main() {
var files = []string{
"file1",
"file2",
}
results, err := readFiles(context.Background(), files)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
for _, result := range results {
fmt.Println(result)
}
}

Building a REST API in Go

In the Node.js space we have a ton of options when it comes to picking a framework for writing HTTP services - Go is no different. After some searching on Google, I chose Gin to start with.

Its interface is similar to Express or Koa, including middleware support, JSON validation and rendering:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import "github.com/gin-gonic/gin"
func main() {
// Creates a router without any middleware by default
r := gin.New()
// By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())
// Recovery middleware recovers from any panics and writes a 500 if there was one.
r.Use(gin.Recovery())
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}

That’s how far I’ve got for now - no production experience, yet. If you feel like that this blog post was useful, and you’d like to learn more on Go, just let me know, and I’ll keep publishing what I learn along my journey tackling the Go language.

Further resources

The above content would not have been possible without following articles:

golang, tutorials
Visit GitHub to add a comment