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:
Let's get to it :)
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 mainimport "fmt"func main() {fmt.Println("hello world")}
To install Go, follow the official setup guide: https://golang.org/doc/install.
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
,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 depbrew 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"
When writing asynchronous JavaScript, we are used to some libraries/language features, like:
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 mainimport ("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,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 mainimport ("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, fileg.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)}}
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 mainimport "github.com/gin-gonic/gin"func main() {// Creates a router without any middleware by defaultr := gin.New()// By default gin.DefaultWriter = os.Stdoutr.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:8080r.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.
The above content would not have been possible without following articles: