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
dep
go get
npm install -g
dep
To get
dep
go get
go get -u github.com/golang/dep/cmd/dep
However, there’s a drawback to using
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
npm init
Before start developing with Go, take some time to set up the
environment variable correctly - you can use the official Go install guide as a reference.GOPATH
dep
npm
Gopkg.toml
package.json
package-lock.json
Gopkg.lock
node_modules
dep
vendor
To add dependencies, you only have to run
dep ensure -add github.com/pkg/errors
toml
[[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
require
func main
ioutil.ReadFile
datFile1
errFile1
fmt.Print
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
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: