Why we use Go to develop Containerum platform for Kubernetes

by Pavel Petrukhin

We have been using Go to develop our cloud platform Containerum for about two years and despite certain challenges, we think that it is a good choice. Containerum Platform consists of a series of smaller services that interact with other components of the system. To ensure this, it is necessary to secure interface compatibility and write code which is easy to read and maintain.

This determined the choice of Go as the main programming language in our company. But not only that — a large part of tools for cloud (Docker, Kubernetes) is also written in Go, which facilitates adding patches and allows using ready components in our code base, e.g. validation, image names parsing, key object models, etc.

Pros

Let’s look into the features that make Go such a good fit for developing cloud services.

Static typing

At first, several of our microservices were written in Python. With all its merits, it places no restrictions to the type of data sent to functions (though the situation is getting better). This makes it difficult to develop systems that work with complex structured data. Go is statically typed. If a function takes a number, you can’t send a string, and vice versa. Subsequently, your program will not explode in production just because you forgot to implement a correct method for the object.

Minimalism

The syntax of Go has been cleared of everything but the bare minimum. It lacks syntax sugar to the extent that you can mistake it for pseudocode. As a result, developers can easily understand not only their code, but the source code written by others as well. It is practically impossible to write spaghetti code — Go prompts you to avoid multiple layers of abstraction to write very ‘down-to-earth’ code.

Powerful standard library

If you need to implement some format or protocol, the first thing to do is to use the standard Go library. It contains ready components for parsing images, data serialization to a variety of formats, compression algorithms, and a lot more. For instance, to write simple CRUD-type services you will need nothing but a standard library, as it has a ready HTTP-server and database drivers.

Linters and utilities

Go comes with several handy tools for checking the executable code. Besides, the community has created a lot of useful tools for code analysis thanks to the powerful syntax parsers in the standard library. Here is a short list of the tools that we use in daily development:

  • go vet — out-of-the-box tool with lots of nice checks from invalid line formatting and style checks to http request validation.
  • goimports — updates your Go import lines, adding missing ones and removing unreferenced ones.
  • go race detector — searches for potential data races in multithreaded code.
  • GAS (go AST scanner) — security checks: unsafe blocks, hardcoded credentials, etc.
  • deadcode — searches for unused code.
  • errcheck — searches for unchecked errors.
  • noice — our tool for generating a boilerplate for error processing.

Performance

Go programs are compiled to native machine code, which makes Go much faster than the majority of other interpreted languages. Besides, the runtime supports code execution according to N:M model and non-blocking input-output for networking, which allows writing fast multithreaded asynchronous code.

Super-fast compilation

Even the biggest Go projects are usually compiled in 40 seconds. Should I mention how much time it saves?

Cons

As any other tool, Go has certain limitation. Some of them are the consequence of the benefits it gives, some are just limitations.

Generics

The first thing a programmer who comes from the world of static typing languages will notice is the lack of metaprogramming possibilities. The language has generics, but only those that are implemented in the standard library: slices, arrays, hash tables, and channels. For all other types you’ll need to use either an empty interface interface{} (analogous to Object in Java) or code generation. It is fair to say that in most cases (network microservices, CLI utilities) you won’t need to use something else other than the described containers.

Error handling

The key idea of handling nonstandard situations in Go is treating errors as data. Each operation can return the required value or error, which the programmer is free to do anything with, like combine with other errors, send to logger or handle it in a more complex way.

It is itself a great idea, especially for asynchronous code. Unfortunately, the authors of Go didn’t include any tools for handling errors in the standard library except for very basic ones. If you want to create error chains or add stack traces, you’ll have to use third-party libraries. To make it worse, you can get a valid value and an error at the same time.

We got around this by writing a mini framework for serializable errors, that can store http code, line value dictionary and an additional set of detailed values. Definition of such sets is generated from a TOML file using noice utility. It allows us to keep the documentation consistent and use a convenient set of methods to send error notifications through all system layers.

Conclusion

To us, the pros of Go outweigh the cons. I hope this article will help you make the right choice in the early stages of your project.

Thanks for reading! You might want to check the Containerum project on GitHub. We need your feedback to make it better — you can submit an issue, or just support the project by giving it a ⭐. Your support really matters to us!

Don’t forget to follow us on Twitter and join our Telegram chat to stay tuned!