When Do We Use Golang in TourRadar?

Oleksandr Yaremchuk
TourRadar
Published in
8 min readMar 11, 2020

Here at TourRadar, we use Go extensively, and it has helped a lot in splitting our monolith. We have found that because of a low learning curve, we can become productive almost from day one.

Last year, after switching to Go to improve scalability and speed for SEO, we decided to gather everyone who uses it to share the knowledge. We called this group “Golang Guild”. Every month, we meet and discuss interesting topics or share our existing challenges. The first topic, which was the most in demand, was “when to use Go?”.

As a result, we decided to share our findings and use cases based on our year of experience using Go in TourRadar.

Advantages

#1 Static types

After switching from PHP to Go, we noticed an improvement in code quality due to having static types. In the PHP monolith, without type hinting, sometimes it’s tricky to predict what parameter type our function will get. The same is applicable when we do math operations on multiple variables. In Go, we can catch most of the errors during compiling. Additionally, we get great bonuses: built-in linters and code formatters.

However, the PHP developer needs to keep in mind that integer divided by integer will be an integer, not float.

1/2 == 0

#2 Compilation to a single binary

We can use a scratch Docker image, which includes basically nothing. As a result, our application becomes a single 7 MB binary file instead of a 700 MB image in PHP. For more information, Lean Golang Docker Images Using Multi-Stage Builds.

#3 It is fast!

It is fast to learn, develop, compile and run. Golang has the speed of a compiled language, but the feel of an interpreted language.

#4 Tools for testing, benchmarking, profiling & documenting

  • No need to choose a testing framework. There is a built-in one.
  • Easy to write integration tests using a mocked HTTP server.
  • Out-of-box benchmarking package.
  • Advanced profiling using pprof.
  • GoDoc — auto-generated documentation.
  • Code generator command.

#5 Concurrency

Go has all the must-have concurrency features:

  • Goroutines
  • Channels
  • Context
  • Atomic counters
  • Mutexes
  • Race condition detector

#6 Culture

The main pillar of Go’s culture is keeping it simple, down-to-earth code without creating many redundant abstractions and putting the maintainability at the top. It’s also a part of the culture to spend the most time actually working on the codebase, instead of tinkering with the tools and the environment. Or choosing between different variations of those.

As it stands in The Zen of Python:

There should be one — and preferably only one — obvious way to do it.

#7 Error handling

After spending time switching from PHP to Go and back, we realized that in Go it’s actually simpler and more obvious to handle errors.

  • Error is just a value, so it’s easy to understand.
  • Starting from Go 1.13 you can wrap errors.
  • It provides an expected behavior, except calling panic().
  • But we can recover() after panic().

Drawbacks

#1 No generics

Due to the absence of generics in Go, we have to write multiple functions with the same implementation but for different types. Alternatively, we can use interface{} and reflect package, which makes our code uglier and slower.

On the other hand, it simplifies and improves the readability of our code.

#2 Lack of memory control

Go is compared with Rust a lot, which has pretty advanced memory control. Go has a built-in garbage collector, and it heavily relies on GC. You can turn it off completely, trigger a garbage collection cycle (Go does this process in parallel), or set up a percentage of heap growth before triggering. There is no more memory management functionality.

We can also do escape analysis of the code to find out how memory will be used by running the following command:

go build --gcflags '-m -l' main.go

On the other hand, relying on GC for memory control helps to simplify development and code readability.

#3 No frameworks.. like Laravel

If a company migrates from PHP to Go, most likely developers will feel a lack of frameworks like Laravel. A framework that provides a project structure and libraries for every use case. There are two big frameworks, such as Gokit and Micro. They mostly solve transport or components interaction problems.

However, generally, Go is used for microservices, so applications written in it don’t need frameworks with bells and whistles. Like we mentioned earlier, keep it simple.

#4 Third-party libraries are not full

Generally, the chance that some SDK for an external API will lack some important features is higher in Golang. Perhaps it’s still a young language. However, this trend is changing and some companies focus more on libraries for Go developers.

Types of applications

CLI applications

We all work with CLI applications written in Go every day. Kubernetes, Docker, Terraform, Grafana and many more examples of projects that changed the way we develop.

Although Python used to be the most popular language for Site Reliability Engineers and DevOps, nowadays, Go is becoming more popular for this specific use case.

You can build advanced monitoring daemons, queue consumers, CLI apps to communicate with an API and compile all of that to just a single binary.

Serverless functions

Go can be used to write serverless functions in various cloud providers, such as AWS Lambda or Google Cloud Functions. It is supported in default runtimes and it has all the necessary libraries to save you time.

Microservices

Although Go is a general purpose language, we find it the most convenient language for Web applications.

It provides a built-in HTTP server, integration testing tools for HTTP servers, template engine, and many more useful features. Goroutines can asynchronously handle all the incoming requests.

Real use cases

API

We mostly use Go to write microservices that expose REST APIs to external clients. By leveraging a serverless architecture with AWS Lambda + API Gateway, we can develop and deploy applications without worrying about authentication, rate limiting, server setup, containers, etc. Everything except the code itself is handled by AWS.

Alternatively, if that architecture is not powerful enough, we can easily replace the transportation layer and make an HTTP or gRPC server.

Processing in the background

Let’s imagine we want to build a simple endpoint that triggers some processing, and we can monitor status using another endpoint.

With Go, it is this simple:

  1. When an application starts, initialize a worker pool for maximum N items running in parallel.
  2. Send POST request to /process endpoint, which returns a unique code for checking updates.
  3. Push to worker pool and process.
  4. Check status using another endpoint and the code received on step 2.

Getting data from multiple sources

In our search service, to always return properly ranked tours, we use the Parallel Run pattern described in Monolith To Microservices, a book written by Sam Newman.

We have multiple sources of rankings: fancy machine learning and old points-based service. Things can fail or be slow, and we don’t want our users to be affected.

That’s why we use the following workflow:

  1. Start multiple goroutines that request rankings from different sources.
  2. Give the preferred source (ML ranker) a time limit using context.WithTimeout().
  3. If the preferred source is successfully processed within a time limit, return the results.
  4. If not, we have another source.

Graceful shutdown

Sometimes we need to process queue messages, and we use Go for such applications. Different things can go wrong: memory consumption is high, instance health is unstable, and load balancer decides to kill this instance. Given that, our application needs to shutdown gracefully without causing issues or corrupt data.

It’s easy to do a graceful shutdown:

  1. Listen for OS signals: SIGINT, SIGTERM, SIGKILL. Notify the app context if something happened and give some time to gracefully shut down wrapping context with context.WithTimeout().
  2. Listen to context.Done() and revert if a context is done.

Conclusions

Go is definitely a good choice when we are on a new project or planning to improve the next one. Once we got used to the Golang development, we made it our main language for writing microservices, because its advantages are far superior to the downsides.

However, there’s no such thing as an all-around perfect programming language. Obviously, we wouldn’t train machine learning models or write iOS applications in Go. It’s not going to be the right fit for every project or every developer.

Sometimes due to deadlines, learning curve or experience, it would be better to use something already known instead of writing in Go from scratch. Even some new services we still write in PHP in spite of migrating to Go. This helps us quickly split monoliths and define a concrete scope of new services.

Go has helped us in building complex apps on time and on budget. We implemented some features which can be notoriously difficult to implement in other languages. Go has helped us a lot in migrating to microservice and serverless architecture. And we have both performant real-time APIs and small Lambda functions written in it.

To sum up, consider the strengths and weaknesses carefully before adopting it as a new language to learn or trying to fold it into your organization’s infrastructure.

--

--