February 8, 2018
|
Under tutorial
(2)
, open-source
(11)

Learning Go as a Node.js Developer

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:

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:

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:

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:

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

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!

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:

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:

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:

  • The Go Programming Langauge
  • https://peter.bourgon.org/go-best-practices-2016/
  • https://golang.github.io/dep
  • https://blog.golang.org/defer-panic-and-recover
  • https://gobyexample.com
  • https://www.golang-book.com/
Did you like this article? Subscribe to get notified about new ones on engineering management, open-source and the web!
No spam. Ever.