Salesforce’s evolving marketing proposition
Discover how the Salesforce Marketing Cloud marketing platform enhances engagement, drives automation, and boosts revenue with marketing automation.
We’ll share our perspective on Go, the language of choice for Avenga products Couper.io and Wao.io.
In our other articles we analyzed other promising languages such as Julia, Rust and Lua.
Jacek Chmiel, Director of Avenga Labs, shares about the Go language, its purpose as well as examples, along with some pros and cons.
Go is an open-source general purpose language which currently (March 2021) is language number 13 on the TIOBE index. The first release was in 2012, then in 2019 and 2020 there were two releases per year.
This language is clearly targeted at Linux, as most of its serious and more famous applications are running on top of various Linux distributions.
It also works on MacOS and Windows, but there are filesystem related problems with the latter.
Go language was created to address common problems with the C and C++ languages when writing server side services. It was designed for complex and reliable large projects utilizing multiprocessor and multicore architectures of modern data centers and workstations.
Go language is focused on code readability and is thus prepared for large teams working on Go projects together.
Go is always compiled to assembly and then to native binaries of the target operating system. It makes Go binaries very fast with no overhead of Just-In-Time compilers.
Go language is statically compiled, and the current version of standard libraries and other dependencies are included. Of course the price of that approach is the increased binary size, but in return there’s a guarantee it will work. There are virtually no problems with depending libraries or versions of dependencies that are so common in the cases of other languages.
Very famous tools such as Docker, Kubernetes, Terraform, OpenShift, Cloudflare, Couchbase, InfluxDB and Soundcloud are developed in Go language.
→ Our take on the latest Kubernetes dropping Docker runtime support – what it means for enterprises
Our entire clouds, including native cloud environments, are running on runtimes and tools written in Go code. There’s no doubt about its usefulness. It’s not an exotic language despite being a relative niche, and it’s a great tool to have.
Our own API management tool and Backend For Frontend tool Couper.IO has been rewritten in Go language, so there’s no question that it can also be used to manage and create various APIs (i.e., REST, gRPC).
There are examples of Go applications for desktop using Qt and other GUI libraries. They are not very popular, but doable.
Applications that are written in such a fast and efficient language, as Go is, can be compiled to webasm and executed in any modern browser.
The philosophy behind Go programming can be described as Keep It Simple (KISS). Anyone with a background in C and C-derived programming languages (C++, Java, C#) should be able to learn Go language quickly.
It’s not to say that Go isn’t very different from them, as it is. The main difference is a very limited set of language features from one side and features baked directly into the language (like collections, maps, concurrency, arrays, slices, etc.) instead of adding them to external libraries (contrary to the majority of other languages).
Go comes with a great tutorial on their main webpage (https://tour.golang.org/welcome/1) which enables developers to discover language features interactively online.
Microsoft Visual Studio’s code has great language support for Go with plugins which are suggested automatically when opening .go files.(One of the Go source files from a Couper.io project).
Here is a fragment of code from a Couper product:
func TestHealth_ServeHTTP(t *testing.T) {
type fields struct {
path string
shutdownCh chan struct{}
}
tests := []struct {
name string
fields fields
req *http.Request
wantStatus int
}{
{"healthy check", fields{shutdownCh: make(chan struct{})}, httptest.NewRequest(http.MethodGet, "/", nil), http.StatusOK},
{"healthy check /w nil chan", fields{}, httptest.NewRequest(http.MethodGet, "/", nil), http.StatusOK},
{"unhealthy check", fields{shutdownCh: make(chan struct{})}, httptest.NewRequest(http.MethodGet, "/", nil), http.StatusInternalServerError},
}
for _, tc := range []testCase{
{"name", "user", "pass", "", "Basic", "", false},
{"name", "user", "", "", "Basic", "", false},
{"name", "", "pass", "", "Basic", "", false},
{"name", "", "", "", "Basic", "", false},
{"name", "user", "pass", "testdata/htpasswd", "Basic", "", false},
{"name", "john", "pass", "testdata/htpasswd", "Basic", "", false},
{"name", "user", "pass", "file", "Basic", "open file: no such file or directory", true},
{"name", "user", "pass", "testdata/htpasswd_err_invalid", "Basic", "basic auth ht parse error: invalidLine: testdata/htpasswd_err_invalid:1", true},
{"name", "user", "pass", "testdata/htpasswd_err_too_long", "Basic", "basic auth ht parse error: lineTooLong: testdata/htpasswd_err_too_long:1", true},
{"name", "user", "pass", "testdata/htpasswd_err_malformed", "Basic", `basic auth ht parse error: malformedPassword: testdata/htpasswd_err_malformed:1: user "foo"`, true},
{"name", "user", "pass", "testdata/htpasswd_err_multi", "Basic", `basic auth ht parse error: multipleUser: testdata/htpasswd_err_multi:2: "foo"`, true},
{"name", "user", "pass", "testdata/htpasswd_err_unsupported", "Basic", "basic auth ht parse error: notSupported: testdata/htpasswd_err_unsupported:1: unknown password algorithm", true},
} {
func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) {
return func(path string) ([]byte, error) {
client := &http.Client{Timeout: timeout}
req, err := http.NewRequest("GET", path, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
defer func() {
if resp != nil {
if e := resp.Body.Close(); e != nil {
log.Println(e)
}
}
}()
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("could not access document at %q [%s] ", path, resp.Status)
}
return ioutil.ReadAll(resp.Body)
}
}
The Go compiler is quite strict and cautious, which is a pain for fast prototyping, but great for large projects.
For instance, if you declare variables and you don’t use them it’s a compiler error . . . not a warning, but an error!
func F() int {
var f = 5
return 0
}
After installing Go, there are multiple elements included out of the box: compiler, testing framework, build chain, lint for code formatting, and a static code analysis tool.
Command go get downloads external libraries by an URI; i.e.,
go get -u github.com/gogo/grpc-example
You can run the formatter to improve the code layout with the built-in command fmt
go fmt mysourcefile.go
On the other hand, developers have voiced their disappointment with the lack of a more advanced package management system such as npm (from node) or pip from Python.
Go has structs and functions, so there are no classes combining methods and encapsulating data. Go means no inheritance, no polymorphism, no virtual methods, no object constructors, etc.
However, simplicity and performance mean trade offs in language capabilities and richness.
Types can be derived (not inherited) from base types but only the value members will be there, not functions. There’s no such thing as methods inherited from the base type.
package main
import (
"fmt"
"strconv"
)
type person struct {
name string
birthYear int
socialID int
}
// function for Person type
func (p person) description() string {
return ("Name: " + p.name + " born " + strconv.Itoa(p.birthYear) + " socialID=" + strconv.Itoa(p.socialID))
}
// type almost equal, almost means no access to "description" function from "base type"
type employee person
func main() {
myPerson := person{name: "Jacek", birthYear: 1995, socialID: 23234234}
fmt.Println("Person data: ", myPerson.description())
var myEmployee employee
myEmployee.name = "Jacek"
myEmployee.birthYear = 1995
myEmployee.socialID = 782834
// will the method from other type work? NO! it won't compile
fmt.Println("Employee data: ", myEmployee.description())
}
Go is focused solely on composition. Types can only contain primitive members of their own and other types.
Types can be embedded, as the example below shows:
package main
import (
"fmt"
"strconv"
)
type person struct {
name string
birthYear int
socialID int
}
// this time we use composition of types
type employee struct {
person
employmentYear int
salary int
}
// function for Person type
func (p person) description() string {
return ("Name: " + p.name + " born " + strconv.Itoa(p.birthYear) + " socialID=" + strconv.Itoa(p.socialID))
}
func main() {
myPerson := person{name: "Jacek", birthYear: 1995, socialID: 23234234}
fmt.Println("Person data: ", myPerson.description())
var myEmployee employee
myEmployee.name = "Jacek"
myEmployee.birthYear = 1995
myEmployee.socialID = 782834
myEmployee.employmentYear = 2019
myEmployee.salary = 1100
// will the method from other types work? YES!
fmt.Println("Employee data: ", myEmployee.description())
}
Please note that there is no implements keyword equivalent known from other languages. There’s no keyword at all.
Duck typing will match the implicit interfaces.
Go is a static type language; there’s reflection and some type of inference, but that’s basically it. No prototypes, dynamic classes, etc. Simplicity, again, is the key.
Go language is strongly criticized for lack of generics, but there are workarounds like empty interfaces. This is probably the biggest language change planned for version 2.
The eternal problem of no values and nulls, unfortunately, is not fixed in Go. NULL problems do apply to Go, which is kind of sad and disappointing because the language is much younger than its NULL-infested predecessors.
Instead of an exception there’s an error return type that carries the information about the improper execution of the routine. It requires writing code explicitly dealing with the errors, which could be annoying for newcomers, but it helps to maintain the discipline because errors cannot be simply ignored. Infamous empty try catch finally, etc. do not exist here.
The Go developer community is approximately two percent of the global population of developers; they are experienced and active. Still, it’s harder to find information online compared to Python, Java, JavaScript or C#.
Felix Hassert, the product director of Wao.io and Couper.io at Avenga, highlights some key reasons for choosing Go for product development.
Go has a very good standard library, covering HTTP, TLS, networking and parsers as well. Those allow you to replace code clusters by a single line of code making coding lightweight and a lot more efficient. It’s a convenient and smart way of programming concurrency like C++ and Java, which is a viable benefit to leverage complex applications.
An interesting fact: two of the Go language co-inventors Robert “Rob” C. Pike and Kenneth “Ken” L. Thompson were a part of the original team who had developed Unix OS.
Looking further, it is fairly versatile and its offering of ease and the speed of the software development process, with simple functions-based syntax, is a major advantage of going with Go programming for server-side requirements. What is also worth mentioning is that Go applications are a suitable choice for a Docker container as fat binaries allow for slim Docker scratch images. All in all, though not without its faults, being pretty minimalistic, clean and readable makes it a language of choice for developers.
As to our recent Avenga experience, we switched to Go language for all custom-built components handling real-time usr traffic for our own product wao.io. Go offers high performance and good resource utilization on multi core systems, especially for IO and network bound tasks. The concurrency primitives give outstanding control over asynchronous processes.The standard library offers perfect support to programs on the network level and allows us to fine-tune the behavior of the HTTP and TLS stack. Last but not least, the built-in profiling tools helped us finding bottlenecks and optimizing.
wao.io is Avenga’s CDN platform aimed at speeding up and securing website delivery. It automatically improves the performance and security of the websites without any additional code changes.
Also, Go language has been a natural choice for us when developing another Avenga product, couper.io, because it was designed for concurrent server processing. Couper.io is used in many docker-based customer setups. The small footprint of the Go binary offers a well-controlled behavior in operations.
couper.io is Avenga’s API gateway, designed for decoupling the frontend from the 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.
There’s a world outside Java, Python, JavaScript and C#. Go programming language is not expected to be an in-place replacement for all those enterprise languages but is a great choice when speed and a memory footprint are essential, and C/C++ complexities are to be avoided.
Yes, there is Rust which also addresses a similar target, but it is much more complex and harder to learn than Go.
Go is easier and built with the team collaboration in mind. Its code is readable and understandable. We appreciate its minimalism and letting go of almost all of the OOP legacy.
At Avenga, we have already written a new version of Couper.IO in Go and published the source code, which is proof of our commitment to this interesting language.
* US and Canada, exceptions apply
Ready to innovate your business?
We are! Let’s kick-off our journey to success!