Give it a GO!

Jacek Chmiel
Avenga
Published in
11 min readMar 30, 2020

Jacek Chmiel, Director of Avenga Labs

Felix Hassert, Director of Avenga Products

To GO or not to GO?

Go [Golang] is ten years old already! Happy birthday, by the way belated, but from the bottom of our Developers’ hearts.

We’ll share our perspective from our Java/C#/JS/C/C++ background together, along with the product director’s opinion, about Go and why he chose to use it for the future development of Avenga products.

Impressions of a newcomer

What is it?

It’s language #9 in popularity and was created by Google, but since 2009 it has been open-source. It runs natively on Linux, MacOS, and Windows, and is a perfect match for containers and cloud-native apps.

What is it for?

Go is targeted for complex and large server side applications requiring fast performance, and it runs on multiple cores.

It’s also ready for large development teams as it heavily promotes code readability.

There is no interpreter and the code is compiled to the native code of a given OS. But don’t worry — the modern compiler is optimized for compilation time, so the compilation times are low.

Dependencies? All the dependencies with correct versions are in the binary output, so there’s no risk of improper dynamic linking between Go components. Problem solved.

Is it working?

Is Kubernetes, Docker, InfluxDB, OpenShift, Terraform, Cloudflare, Couchbase, and Soundcloud working?

Yes, all these famous apps and services are written in Go. So, there’s no question if they work or not. The majority of cloud-native applications run on engines created by using Go.

But what about web APIs like REST APIs? There are frameworks for that and it’s perfectly doable. For the internal APIs, there’s also support for gRPC (Google vs. Google).

What about the client side (browser)? You can compile Go code to webassembly for the fastest possible performance and transpile it to JavaScript for compatibility. It’s feasible but not very popular at the moment, but it’s worth remembering in the context of Webasm becoming a fourth language of the browser.

Isn’t it too difficult?

This language is simplified and minimalistic, so anyone with a background in Java/C#/C/C++ can learn it very quickly. In fact, it’s really more about learning which features are not available and what are the new ways of doing things, especially collections/maps/arrays and concurrency, that are both built into the language and not added later as libraries.

You do have to switch to declare types after the variables, not before, but after a few hours it’s doable ;-) i.e. var name2 string

There’s an excellent tutorial about the language itself on the Golang webpage, called A Tour of Go, so I won’t be repeating those lessons here. The article is focused on sharing impressions anyway.

What to like

Let me share what I personally like, as you might have a different point of view:

  1. Static typing — you can detect errors in the code before runtime.

2. Yet — type inference is still there with := operator.

3. Native speed of local OS — in the tests against Python, for instance, it’s 5–10x times faster.
(https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/go-python3.html )

4. Memory footprint is also very low like 5x-10x less than interpreted dynamic languages (Python).
(https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/go-python3.html )

5. Functions can return multiple values including complex structs.

6. Add go before the function call and you start goroutine — concurrent function execution mechanism. Built-in!

7. Passing messages between goroutines — use channels (native) to send and receive messages from different, blocking, or non-blocking — your choice. You can use channels as a synchronization mechanism as well.

package mainimport (
"fmt"
"math/rand"
"strconv"
"strings"
"time"
)
func processLargeMessage(id int, outputMessage chan string) {
fmt.Println("Started processing large message...", id)
time.Sleep(time.Millisecond * 100 * time.Duration(rand.Intn(50)))
fmt.Println("done...", id)
var messageBuilder strings.Builder
messageBuilder.WriteString("this is output message for id")
messageBuilder.WriteString(strconv.Itoa(id))
outputMessage <- messageBuilder.String()
}
func main() {
var id = 1
// build channel
outputMessages := make(chan string)
// launch function in goroutines
go processLargeMessage(id, outputMessages)
id++
go processLargeMessage(id, outputMessages)
id++
go processLargeMessage(id, outputMessages)
// read output messages
fmt.Println(<-outputMessages)
fmt.Println(<-outputMessages)
fmt.Println(<-outputMessages)
}

8. Some compare Go concurrency to Haskel, which is much harder to learn for regular devs.

9. Functions are first-class citizens, allowing for programming in a functional style.

10. No magic or a lack of magic; no factories that create factories to create an object, the code is very easy to read and understand.

11. Go is easier to read than to write. Tried Go with several GitHub projects and it does read well. Code readability is the most important feature in large projects, as more and more time is spent on reading and modifying code than writing a new one.

12. There’s a garbage collector, so despite similarities to C you don’t have to manage the memory yourself. The Go community claims that there are little to no stutters in the Go garbage collector, which is much better than Java GC in their opinion.

13. Compatibility promise — no breaking changes, code written in Go 1.00 is expected to run in 1.84 or newer.

14. No licensing problems and fears (Java anyone?).

15. Perfect for containers — no memory and CPU hungry VMs and interpreters, it runs well on Docker and Kubernetes (Go vs Go).

Interesting facts

  1. There’s only one loop — it’s for which can work as for and while depending on parameters
func main() {for i := 0; i < 10; i++ {    fmt.Println(“i=”, i)}var j = 0for j < 10 {    fmt.Println(“j=”, j)    j++}var k = 0for {    fmt.Println(“k=”, k)    k++    if k > 9 {       break    }}}

2. You can defer function statement execution with a defer statement so it will execute no matter what (error) is at the end.


defer fmt.Println(“I will be executed anyway!”)

3. Usually semicolons ; are implicit — you don’t have to type them (most of the time).

4. Tabs vs. spaces → tabs!

5. Methods with a Capital Letter are exported from the package as a convention, i.e., Println from fmt package.

6. There’s no try, catch, finally, or no exceptions — seriously.

package mainimport (    “errors    “fmt”    “math”)func sqrt(n float64) (float64, error) {    if n < 0 {        return -1, errors.New(“function parameter must be greater or equal to zero”)}    return math.Sqrt(n), nil}func main() {    // use multiple return values and error interface
var result1, err = sqrt(-1)
// was there an error?
if err != nil {
fmt.Println(“Error occurred ‘“, err, “‘“) fmt.Println(“However there’s return value of”, result1) } else { fmt.Println(“Result =”, result1) }}

7. It was very strange for me to write the word struct so often. The last time I wrote it was in the last century ;-)

8. The mascot of the Go is a gopher.

9. It’s open source, so it’s not Google proprietary (one of the common myths).

Compiler is your friend

Compiler is fast and gives useful type checking and it tends to be very strict. I consider it to be really good, especially for large projects.

For instance, if you declare variables and you don’t use them — it’s a compiler error, not a warning, but error!

func A() int {    var a = 2    return 0}

Everything in the box

In most of the cases you don’t have to look elsewhere — you get a compiler, testing framework, build chain, lint for code formatting, and static code analysis tool by default.

There are no separate makefiles, no npm, and no external dependencies that keep breaking all the time.

Simple go get will get you the external libraries given the proper URI; i.e.,

go get -u github.com/gogo/grpc-example

You can run go command to format your code properly

go fmt myuglyfile.go

What to dislike

Where are my OOP features?

OOP features are missing. Let’s imagine you are an Object Oriented Programming (OOP) purist. It’s going to hurt, then.

Go language is based around structs and functions, not classes and objects. There is no inheritance, no polymorphism, no virtual methods, no object constructors, etc. There are ways around it though, for instance, different language constructs (like embedded types). Simplicity and performance come at the price of dropping dynamic elements in the language. Is it worth it? I believe it is.

Types can be derived from base types but only the value members will be “inherited”, not functions. And yes, the methods won’t be available in the “derived” type!

type Vehicle struct {    maxSpeed int    weight int    maxRange int}// function for Vehicle typefunc (v Vehicle) timeToReachMaximumRange() float32 {    return float32(v.maxRange) / float32(v.maxSpeed)}// Car type, “almost” the same as Vehicletype Car Vehiclefunc main() {    myVehicle := Vehicle{maxSpeed: 220, weight: 1800, maxRange: 1000}    fmt.Println(“Time”, myVehicle.timeToReachMaximumRange())    myCar := Car{maxSpeed: 220, weight: 1800, maxRange: 1000}    // this won’t work!    fmt.Println(“Time”, myCar.timeToReachMaximumRange())}

Go promotes composition over inheritance, not just by the lack of inheritance. Composition over inheritance has already been the preferred way to build object models for some years. Types can contain primitive members of their own and other types.

So, in the example below, you can embed another type inside.

type Vehicle struct {    maxSpeed int    weight int    maxRange int}func (v Vehicle) timeToReachMaximumRange() float32 {    return float32(v.maxRange) / float32(v.maxSpeed)}type Car struct {    Vehicle    carIdNumber int64}func main() {    myVehicle := Vehicle{maxSpeed: 220, weight: 1800, maxRange: 1000}    fmt.Println(“Time”, myVehicle.timeToReachMaximumRange())    var myCar Car    myCar.maxSpeed = 260
myCar.maxRange = 650
myCar.weight = 3200
// this works now!
fmt.Println(“Car”, myCar, “Time”,myCar.timeToReachMaximumRange())
}

By the way, you don’t even have to implement an interface!

You can define interfaces and types that will ‘meet’ those interface definitions — duck typing will do the rest.

Let’s imagine you are a dynamic languages fan. It’s going to hurt as well. Go is a static type language; there’s a reflection and some type of inference, but that’s basically it.

Go also has pretty poor support for generic programming, using a workaround, in fact, like empty interfaces, etc.[It is planned for go 2].

Null/nil problem

A null problem is an eternal problem of non-values, null values, etc. In Go it’s a nil problem. As many Go critics note that it’s a shame that modern languages repeat some key mistakes of the past languages by creating another “null” problem reincarnation. The compiler cannot easily protect against a nil-related error that would hit your implementation in runtime.

Lack of exceptions

No exceptions? Really? Yes — no exceptions. There’s an error return type that carries the information about the improper execution of the routine. Is it so bad? I don’t think so and neither do the authors of Go — exceptions are thrown all the time but caught very rarely. They got rid of the mechanism altogether. But for the newcomers, it means another habit you have to get rid of.

Lack of libraries

There are very good libraries for writing servers of any kind; http, gRPC, UDP, etc. It’s where Go shines — servers, services, and APIs.

However, when it comes to Machine Learning, so far, nothing can compete with Python, and this particular area is a weak spot of Go at the moment.

Tensorflow (also from Google) provides Go API, but lacks the API stability guarantees, etc. So, it is there, but it is far less popular than Python API.

Smaller dev community

Another business-related factor playing against Go is the relatively small developers’ community; it is very active, though.

By the way, the language is really easy to learn for the dominating Java/JS/C#/C/C++ community, so at the time of polyglots, the majority of backend developers can easily add it to their language skill portfolios.

As you can see, this “do not like list” is designed for a special audience — mostly OOP traditionalists. And the majority of the items on this list (except the nil problem) are considered as pros by many.

Why GO for the future of Wao.io and Couper.IO?

Pics of Felix

Felix Hassert, the product director of Wao.io and Couper.io shares his reasons for choosing Go for product development:

  • Excellent standard library which covers networking, HTTP, TLS, connection pools, and parsers (often with no need for the 3rd party libs).
  • Type safety and compiler feedback, but still fast roundtrips (no building/linking complexity).
  • Convenient and smart way of programming concurrency.
  • Not an absolute beauty, but clean, pragmatic and readable.
  • Made for server-side internet programming.
  • Fat binaries allow for slim Docker scratch images.
  • It’s fast and fun, after all.

wao.io is Avenga’s CDN platform that accelerates and secures web site delivery. It automatically improves the performance and security of your website without any code changes. It employs state-of-the-art techniques such as Web-optimized networking, advanced media compression, edge caching, automatic HTTPS upgrading, web firewalls and much more.

We plan to switch to Go for all custom-built components that handle real-time user traffic. Go offers high performance and good resource utilization on multi core systems, especially for IO and network bound tasks. The standard library offers excellent support to programs on the network level and allows us to fine-tune the behavior of the HTTP and TLS stack.

couper.io is Avenga’s API gateway, designed for decoupling frontend from backend by implementing the backend-for-frontend design pattern. It serves as a Web API and needs to implement a lot of Web standard protocols. Go has been a natural choice for us because it was designed for concurrent server processing. Couper is used in many docker-based customer setups. The small footprint of the Go binary offers a well-controlled behavior in operations.

Summary

Despite our sentiments for Go in Avenga, it would be a pretty hard job to find decent arguments for migrating legacy business apps from OOP rich languages like Java or C# to Go, as Go by its nature wasn’t meant to be a feature-rich successor of Java (for instance).

If you prefer faster APIs with a low memory footprint that are perfect for cloud-native applications, why don’t you give a Golang a go. It’s very easy to learn, pleasant to read and code. It is very fast in debugging and contains excellent built-in features and libraries.

Golang is finding its way into new projects and when the time comes to write those backend APIs again, I’d recommend taking Golang into consideration before another painful refactoring of APIs and creating API 2.0, which often means rewriting a lot of code anyway.

At Avenga, we are not trying Go (as in famous “Do or do not, there is no try” concept), we do use it with great success in our products and projects and plan to continue doing so in the future.

Read how we deliver technology solutions with a clear approach to streamline and accelerate your business.

--

--